Page Screenshot (Playwright)
page_screenshotCapture a PNG screenshot of any URL using headless Chromium. Supports element selectors, full-page capture, dark mode, and custom viewports.
Instructions
Launch headless Chromium, navigate to a URL, and save a PNG screenshot. Supports element selectors, full-page capture, dark mode, and custom viewports. Requires the optional playwright peer dependency.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | URL to screenshot. | |
| outPath | Yes | Absolute file path to write PNG to. Parent dirs will be created. | |
| selector | No | CSS selector to screenshot instead of the viewport. | |
| fullPage | No | Capture the full scrollable page (default: false). | |
| width | No | Viewport width (default 1280). | |
| height | No | Viewport height (default 800). | |
| deviceScaleFactor | No | DPR (default 2). | |
| colorScheme | No | ||
| waitForSelector | No | Wait for this selector before capturing. | |
| timeoutMs | No |
Implementation Reference
- src/tools/page-screenshot.ts:25-88 (handler)Handler function that registers the 'page_screenshot' tool with the MCP server. Contains the full implementation: launches headless Chromium via Playwright, navigates to the URL, optionally waits for a selector, captures a screenshot (viewport or element), writes it to disk, and returns metadata (file size, viewport, etc.).
export function registerPageScreenshot(server: McpServer) { server.registerTool( "page_screenshot", { title: "Page Screenshot (Playwright)", description: "Launch headless Chromium, navigate to a URL, and save a PNG screenshot. Supports element selectors, full-page capture, dark mode, and custom viewports. Requires the optional `playwright` peer dependency.", inputSchema: InputShape, }, async (args) => { try { const pw = await loadOptional<typeof import("playwright")>( "playwright", "npm i -D playwright && npx playwright install chromium" ); const browser = await pw.chromium.launch(); try { const context = await browser.newContext({ viewport: { width: args.width ?? 1280, height: args.height ?? 800, }, deviceScaleFactor: args.deviceScaleFactor ?? 2, colorScheme: args.colorScheme ?? "light", }); const page = await context.newPage(); const timeout = args.timeoutMs ?? 20000; await page.goto(args.url, { waitUntil: "networkidle", timeout }); if (args.waitForSelector) { await page.waitForSelector(args.waitForSelector, { timeout }); } await fs.mkdir(path.dirname(args.outPath), { recursive: true }); if (args.selector) { const el = await page.$(args.selector); if (!el) return errorResult(`Selector not found: ${args.selector}`); await el.screenshot({ path: args.outPath }); } else { await page.screenshot({ path: args.outPath, fullPage: args.fullPage ?? false, }); } const stat = await fs.stat(args.outPath); return jsonResult({ url: args.url, outPath: args.outPath, bytes: stat.size, viewport: { width: args.width ?? 1280, height: args.height ?? 800, deviceScaleFactor: args.deviceScaleFactor ?? 2, }, colorScheme: args.colorScheme ?? "light", fullPage: args.fullPage ?? false, selector: args.selector ?? null, }); } finally { await browser.close(); } } catch (err) { return errorResult(err instanceof Error ? err.message : String(err)); } } );