# Directory Structure ``` ├── .gitignore ├── bun.lock ├── CLAUDE.md ├── index.ts ├── LICENSE ├── package.json ├── README.md └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | # dependencies (bun install) 2 | node_modules 3 | 4 | # output 5 | out 6 | dist 7 | *.tgz 8 | 9 | # code coverage 10 | coverage 11 | *.lcov 12 | 13 | # logs 14 | logs 15 | _.log 16 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 17 | 18 | # dotenv environment variable files 19 | .env 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | .env.local 24 | 25 | # caches 26 | .eslintcache 27 | .cache 28 | *.tsbuildinfo 29 | 30 | # IntelliJ based IDEs 31 | .idea 32 | 33 | # Finder (MacOS) folder config 34 | .DS_Store 35 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | [](https://mseep.ai/app/newbeb-stealth-browser-mcp) 2 | 3 | # Stealth Browser MCP Server 4 | 5 | An [MCP (Model Context Protocol)](https://modelcontextprotocol.ai) server that provides stealth browser capabilities using Playwright with anti-detection techniques. This server allows MCP clients to navigate to websites and take screenshots while evading common bot detection systems. 6 | 7 | <a href="https://glama.ai/mcp/servers/efxcqjoq01"> 8 | <img width="380" height="200" src="https://glama.ai/mcp/servers/efxcqjoq01/badge" alt="Stealth Browser Server MCP server" /> 9 | </a> 10 | 11 | ## Features 12 | 13 | - **Stealth Mode**: Uses [puppeteer-extra-plugin-stealth](https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth) with [playwright-extra](https://github.com/berstend/puppeteer-extra/tree/master/packages/playwright-extra) to bypass bot detections 14 | - Modifies browser fingerprints to appear as regular user traffic 15 | - Handles WebGL, canvas, font, plugin and other browser fingerprinting techniques 16 | - **Screenshot Tool**: Take full-page or element-specific screenshots of any website 17 | - Supports both headless (default) and visible browser modes 18 | - **MCP Integration**: Exposes browser capabilities via Model Context Protocol 19 | 20 | ## Installation 21 | 22 | ```bash 23 | # Install dependencies 24 | bun install 25 | ``` 26 | 27 | ## Usage 28 | 29 | ```bash 30 | # Run the MCP server 31 | bun start 32 | 33 | # Development mode 34 | bun dev 35 | 36 | # Inspect available tools 37 | bun inspect 38 | ``` 39 | 40 | ## Available Tools 41 | 42 | ### screenshot 43 | 44 | Takes screenshots of webpages using a stealth browser. 45 | 46 | Parameters: 47 | - `url` (string, required): The URL to navigate to 48 | - `fullPage` (boolean, optional, default: true): Whether to capture the entire page 49 | - `selector` (string, optional): CSS selector to capture only a specific element 50 | - `headless` (boolean, optional, default: true): Whether to run in headless mode or visible browser mode 51 | 52 | ## Technical Details 53 | 54 | This project uses: 55 | - [FastMCP](https://github.com/punkpeye/fastmcp) for the MCP server implementation 56 | - [Playwright](https://playwright.dev/) for browser automation 57 | - [playwright-extra](https://github.com/berstend/puppeteer-extra/tree/master/packages/playwright-extra) for plugin support 58 | - [puppeteer-extra-plugin-stealth](https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth) for avoiding bot detection 59 | 60 | --- 61 | 62 | This project was built with [Bun](https://bun.sh), a fast all-in-one JavaScript runtime. ``` -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- ```markdown 1 | # stealth-browser-mcp Guidelines 2 | 3 | ## Workflow 4 | - Use a git feature based workflow and always work in a feature and make sure we commit things along the way. 5 | - Don't mix features in a single branch. If we need to work on multiple features, create separate branches. 6 | - If we learn something along the way about how to work better please share it with me and if I agree you can add it to this file. Don't ever add anything here with my specific approval. 7 | - You've got tools, use them! 8 | 9 | ## Commands 10 | - **Run**: `bun run index.ts` - Run the main application 11 | - **Dev**: `bun --watch index.ts` - Run with live reloading 12 | 13 | ## Code Style 14 | - **Imports**: Use named imports, sort alphabetically, group by type 15 | - **Formatting**: 2-space indentation, trailing semicolons 16 | - **Types**: Prefer explicit typing with interfaces over types when possible 17 | - **Naming**: camelCase for variables/functions, PascalCase for classes/types 18 | - **Error Handling**: Use try/catch with specific error types, avoid generic catches 19 | - **Comments**: JSDoc for public APIs, inline for complex logic 20 | - **Async**: Prefer async/await over raw Promises 21 | - **Stealth Browser**: Follow puppeteer-extra plugin patterns for stealth features 22 | 23 | ## Project Structure 24 | - `index.ts`: Main application entry point 25 | 26 | This project uses Bun and TypeScript with ESM modules. 27 | ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | // Enable latest features 4 | "lib": ["ESNext", "DOM"], 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "moduleDetection": "force", 8 | "jsx": "react-jsx", 9 | "allowJs": true, 10 | 11 | // Bundler mode 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "verbatimModuleSyntax": true, 15 | "noEmit": true, 16 | 17 | // Best practices 18 | "strict": true, 19 | "skipLibCheck": true, 20 | "noFallthroughCasesInSwitch": true, 21 | 22 | // Some stricter flags (disabled by default) 23 | "noUnusedLocals": false, 24 | "noUnusedParameters": false, 25 | "noPropertyAccessFromIndexSignature": false 26 | } 27 | } 28 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "stealth-browser-mcp", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "main": "index.ts", 6 | "module": "index.ts", 7 | "author": "Brian Lloyd-Newberry", 8 | "email": "[email protected]", 9 | "description": "A MCP Server that provides browser access through playwright with \"stealth mode\" enabled.", 10 | "scripts": { 11 | "start": "bun run index.ts", 12 | "dev": "fastmcp dev index.ts", 13 | "inspect": "fastmcp inspect index.ts" 14 | }, 15 | "devDependencies": { 16 | "@modelcontextprotocol/inspector": "0.4.1", 17 | "@types/bun": "latest", 18 | "@wong2/mcp-cli": "1.4.0", 19 | "tsx": "^4.19.3" 20 | }, 21 | "peerDependencies": { 22 | "typescript": "^5" 23 | }, 24 | "dependencies": { 25 | "fastmcp": "^1.19.0", 26 | "playwright": "^1.50.1", 27 | "playwright-extra": "^4.3.6", 28 | "puppeteer-extra-plugin-stealth": "^2.11.2", 29 | "zod": "^3.24.2" 30 | } 31 | } 32 | ``` -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { FastMCP } from 'fastmcp'; 2 | import { z } from 'zod'; 3 | import { chromium } from 'playwright-extra'; 4 | import StealthPlugin from 'puppeteer-extra-plugin-stealth'; 5 | 6 | // Add the stealth plugin to playwright 7 | chromium.use(StealthPlugin()); 8 | 9 | // Create a new FastMCP server 10 | const server = new FastMCP({ 11 | name: 'stealth-browser-mcp', 12 | version: '1.0.0' 13 | }); 14 | 15 | // Add the screenshot tool 16 | server.addTool({ 17 | name: 'screenshot', 18 | description: 'Navigate to a URL and take a screenshot of the webpage', 19 | parameters: z.object({ 20 | url: z.string().describe('URL to navigate to'), 21 | fullPage: z.boolean().default(true).describe('Whether to take a screenshot of the full page'), 22 | selector: z.string().optional().describe('CSS selector to screenshot a specific element'), 23 | headless: z.boolean().default(true).describe('Whether to run browser in headless mode (default) or visible mode') 24 | }), 25 | execute: async ({ url, fullPage = true, selector, headless = true }) => { 26 | // Launch browser with stealth mode 27 | const browser = await chromium.launch({ headless }); 28 | try { 29 | const page = await browser.newPage(); 30 | 31 | // Navigate to the URL 32 | console.error(`Navigating to ${url}...`); 33 | await page.goto(url, { waitUntil: 'networkidle' }); 34 | 35 | // Take the screenshot 36 | const screenshotOptions = { fullPage }; 37 | let screenshot; 38 | 39 | if (selector) { 40 | // Screenshot specific element if selector is provided 41 | const element = await page.$(selector); 42 | if (element) { 43 | screenshot = await element.screenshot(); 44 | } else { 45 | throw new Error(`Element with selector '${selector}' not found`); 46 | } 47 | } else { 48 | // Screenshot entire page 49 | screenshot = await page.screenshot(screenshotOptions); 50 | } 51 | 52 | // Return screenshot as base64 data with content type 53 | return { 54 | content: [ 55 | { 56 | type: 'image', 57 | data: screenshot.toString('base64'), 58 | mimeType: 'image/png' 59 | } 60 | ] 61 | }; 62 | } finally { 63 | await browser.close(); 64 | } 65 | } 66 | }); 67 | 68 | // Start the server with STDIO transport 69 | server.start({ transportType: 'stdio' }).then(() => { 70 | console.error('MCP server started and waiting for commands...'); 71 | }).catch(error => { 72 | console.error('Failed to start MCP server:', error); 73 | }); ```