Build an MCP screenshot service as a narrow tool that validates a URL and capture options, runs an isolated Playwright browser context, and returns a bounded image artifact. The fastest starting point is Node.js with Playwright; for a service clients reach over a network, expose MCP over HTTP, authenticate at the edge, and enforce quotas in the worker layer.
The important engineering work is not calling page.screenshot(). It is deciding which destinations clients may reach, what inputs the tool accepts, how much browser work a request can consume, and what happens when an image is too large to return inline.
Choose what you are building: a local tool or a remote service
A local MCP server launched by a client is a good first milestone: it lets a trusted user request captures from a local integration without operating a network service. For network access by multiple clients, use the documented HTTP endpoint, put authentication and authorization at the edge, and enforce per-tenant limits in the worker layer. Do not treat a local stdio prototype as a safe public service; a remote service has a different threat model and scaling requirement.
There is also an official Playwright MCP package, @playwright/mcp. Its standard client configuration invokes it with npx and requires Node.js 20 or newer:
Free tools Windows power users keep installed
One-click scans. No signup required.
#1 Best Overall
- Includes Raspberry Pi 5 with 2.4Ghz 64-bit quad-core CPU (8GB RAM)
- Includes 128GB Micro SD Card pre-loaded with 64-bit Raspberry Pi OS, USB MicroSD Card Reader
- CanaKit Turbine Black Case for the Raspberry Pi 5
- CanaKit Low Noise Bearing System Fan
- Mega Heat Sink - Black Anodized
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest"]
}
}
}
That is a quick way to try Playwright MCP. If you need a purpose-built screenshot service with your own request contract, policy checks, artifact handling, and tenant limits, create a dedicated screenshot tool rather than exposing a general-purpose browser-control surface.
Design a narrow screenshot tool contract
Give the MCP tool one job, such as capture_screenshot. Make its description and schema explicit about URL schemes, supported formats, maximum dimensions, whether full-page capture is allowed, and whether the server permits navigation outside an approved set of sites. A practical request might look like this:
{
"url": "https://example.com",
"format": "png",
"fullPage": true,
"viewport": { "width": 1280, "height": 720 },
"scale": "css",
"waitUntil": "networkidle"
}
These fields are not harmless metadata. A URL determines where the server connects; a very wide viewport or tall full-page image can consume memory; a wait condition can hold a browser slot; and a response format affects the output size. Validate every value on the server even if the MCP client already validates against a JSON schema.
Keep the contract useful but bounded
- URL: accept only intended schemes, typically HTTPS and, if your policy requires it, HTTP. Reject unsupported schemes and destinations your service should never contact.
- Viewport: require positive integer width and height, then clamp them to service-defined maxima.
- Capture target: support either a full page or a CSS selector for one element. If a selector is requested, fail clearly when it does not resolve rather than silently capturing an unrelated page.
- Format and scale: allow only PNG, JPEG, or WebP if your output pipeline supports them; define whether scaling means CSS pixels or device pixels.
- Readiness: expose a small set of wait policies, such as a named navigation readiness state or a bounded delay. Do not accept arbitrary browser code as a convenience option.
- Output: set a maximum byte count. Return image content inline only when it fits the MCP response budget; otherwise save a short-lived artifact and return its reference and relevant metadata.
Also set limits for full-page pixel count, navigation duration, redirect count, response bytes, and concurrent jobs. These are service policy decisions, not safe defaults that can be left to the caller.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minutePC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Rank #2
- Includes Raspberry Pi 4 4GB Model B with 1.5GHz 64-bit quad-core CPU (4GB RAM)
- Includes Pre-Loaded 32GB EVO+ Micro SD Card (Class 10), USB MicroSD Card Reader
- CanaKit Premium High-Gloss Raspberry Pi 4 Case with Integrated Fan Mount, CanaKit Low Noise Bearing System Fan
- CanaKit 3.5A USB-C Raspberry Pi 4 Power Supply (US Plug) with Noise Filter, Set of Heat Sinks, Display Cable - 6 foot (Supports up to 4K60p)
- CanaKit USB-C PiSwitch (On/Off Power Switch for Raspberry Pi 4)
Implement the first trusted-client version
The example below is a small stdio server for a trusted local client. It creates a fresh browser context per request, accepts only exact hostnames configured in an allowlist, validates capture inputs, applies a deadline, and returns a base64-encoded image as MCP image content. It deliberately does not provide arbitrary JavaScript execution or a general-purpose browser tool.
Use Node.js 20 or newer. In an empty project, install the MCP SDK and Playwright, then install the browser runtime supported by your environment:
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/sdk playwright
npx playwright install chromium
Save the following as server.js. The host allowlist is intentionally restrictive: set SCREENSHOT_ALLOWED_HOSTS to comma-separated, exact hostnames such as example.com,docs.example.com. This is a useful first boundary for known destinations, not a complete defense against DNS rebinding or unsafe network routing; the remote-service section explains the additional control required for deployment.
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { chromium } from "playwright";
const allowedHosts = new Set(
(process.env.SCREENSHOT_ALLOWED_HOSTS ?? "")
.split(",")
.map((host) => host.trim().toLowerCase())
.filter(Boolean),
);
const MAX_WIDTH = 1920;
const MAX_HEIGHT = 1080;
const MAX_IMAGE_BYTES = 5 * 1024 * 1024;
const DEADLINE_MS = 30_000;
function validateInput(input) {
if (!input || typeof input !== "object") throw new Error("Input must be an object.");
if (typeof input.url !== "string") throw new Error("url must be a string.");
const target = new URL(input.url);
if (target.protocol !== "https:" && target.protocol !== "http:") {
throw new Error("Only http and https URLs are supported.");
}
if (target.username || target.password) throw new Error("Credentials in URLs are not allowed.");
if (!allowedHosts.has(target.hostname.toLowerCase())) {
throw new Error("This hostname is not in the service allowlist.");
}
const width = input.viewport?.width ?? 1280;
const height = input.viewport?.height ?? 720;
if (!Number.isInteger(width) || width < 1 || width > MAX_WIDTH) {
throw new Error(`viewport.width must be an integer from 1 to ${MAX_WIDTH}.`);
}
if (!Number.isInteger(height) || height < 1 || height > MAX_HEIGHT) {
throw new Error(`viewport.height must be an integer from 1 to ${MAX_HEIGHT}.`);
}
const format = input.format ?? "png";
if (!["png", "jpeg", "webp"].includes(format)) {
throw new Error("format must be png, jpeg, or webp.");
}
const waitUntil = input.waitUntil ?? "domcontentloaded";
if (!["load", "domcontentloaded", "networkidle"].includes(waitUntil)) {
throw new Error("waitUntil must be load, domcontentloaded, or networkidle.");
}
if (input.element !== undefined && typeof input.element !== "string") {
throw new Error("element must be a CSS selector string.");
}
if (input.element && input.fullPage) {
throw new Error("Choose either an element capture or fullPage capture, not both.");
}
return {
target,
width,
height,
format,
waitUntil,
fullPage: input.fullPage === true,
element: input.element,
scale: input.scale === "device" ? "device" : "css",
};
}
const server = new Server(
{ name: "screenshot-service", version: "1.0.0" },
{ capabilities: { tools: {} } },
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
name: "capture_screenshot",
description: "Capture an image of an allowlisted HTTP(S) URL. Supports bounded viewport dimensions, PNG/JPEG/WebP, full-page capture, or one CSS element. Does not execute caller-supplied JavaScript.",
inputSchema: {
type: "object",
properties: {
url: { type: "string", description: "HTTP(S) URL on an approved hostname." },
format: { type: "string", enum: ["png", "jpeg", "webp"] },
fullPage: { type: "boolean" },
viewport: {
type: "object",
properties: {
width: { type: "integer", minimum: 1, maximum: MAX_WIDTH },
height: { type: "integer", minimum: 1, maximum: MAX_HEIGHT },
},
},
scale: { type: "string", enum: ["css", "device"] },
waitUntil: { type: "string", enum: ["load", "domcontentloaded", "networkidle"] },
element: { type: "string", description: "Optional CSS selector for one element; cannot be combined with fullPage." },
},
required: ["url"],
additionalProperties: false,
},
}],
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name !== "capture_screenshot") {
throw new Error(`Unknown tool: ${request.params.name}`);
}
let args;
try {
args = validateInput(request.params.arguments);
} catch (error) {
return { isError: true, content: [{ type: "text", text: error.message }] };
}
const browser = await chromium.launch({ headless: true });
let context;
try {
context = await browser.newContext({
viewport: { width: args.width, height: args.height },
deviceScaleFactor: args.scale === "device" ? 2 : 1,
});
const page = await context.newPage();
page.setDefaultTimeout(DEADLINE_MS);
await page.goto(args.target.href, { waitUntil: args.waitUntil, timeout: DEADLINE_MS });
let bytes;
if (args.element) {
const locator = page.locator(args.element);
if (await locator.count() !== 1) throw new Error("The element selector must match exactly one element.");
bytes = await locator.screenshot({ type: args.format, timeout: DEADLINE_MS });
} else {
bytes = await page.screenshot({
type: args.format,
fullPage: args.fullPage,
timeout: DEADLINE_MS,
});
}
if (bytes.byteLength > MAX_IMAGE_BYTES) {
return { isError: true, content: [{ type: "text", text: "Image exceeds the 5 MiB inline response limit; reduce the viewport or capture a smaller target." }] };
}
const mimeType = args.format === "jpeg" ? "image/jpeg" : `image/${args.format}`;
return {
content: [{ type: "image", data: bytes.toString("base64"), mimeType }],
};
} catch (error) {
return { isError: true, content: [{ type: "text", text: `Screenshot failed: ${error.message}` }] };
} finally {
await context?.close().catch(() => {});
await browser.close().catch(() => {});
}
});
await server.connect(new StdioServerTransport());
Launch it with an allowlist in the server process environment, not by accepting an allowlist value from the tool caller:
Rank #3
- 【What you Get】You will get 1*Pi 5 8GB Single Board,1*RasTech Case,1*Active Cooler,1*Screwdriver,1*Installation instructions,12-month free warranty, lifetime service, 24-hour prompt and friendly response.
- 【More Connectors】There are two USB 3.0 ports(5Gbps simultaneously) and two USB 2.0 ports, which triple total bandwidth ,support any combination of up to two cameras or displays. Peak SD card performance is doubled through support for the SDR104 high-speed mode. It provides a smooth desktop experience for you. Offer Gigabit Ethernet and a PCIe interface, along with dual-band Wi-Fi and Bluetooth 5.0/BLE wireless capability. The RasTech Pi 5 Kit use the new 27W 5.1V 5A USB-C power connector.
- 【 Support Dual 4Kp60 Display 】Each of the two microHDMI sockets can control a 4K display at 60 Hertz, now support HDR, offering super HD video for media streaming projects. RPi 5 is the first RPi model that comes with a PCI Express port (PCIe 2.0 x1 with 500 MB/s) to attach SSDs (requires separate M.2 HAT).
- 【 Excellent Chips And Applications】Pi 5 is a full-size Pi computer using silicon built in-house at Pi. The RP1 “southbridge” provides the bulk of the I/O capabilities for Pi 5. Pi 5 is more friendly and convenient in the development of Internet of Things, Web development, machine identification, automatic control and other electronic equipment applications and network.
- 【 Faster CPU, Better GPU 】 Pi 5 features a Broadcom BCM2712 64-bit quad-core Arm Cortex-A76 processor running at 2.4GHz, it delivers a 2–3× increase in CPU performance relative to RaspberryPi 4. The 800MHz VideoCore VII GPU is compatible to OpenGL ES 3.1 and Vulkan 1.2, substantial uplift in graphics performance. Pi 5 Offers lightning-fast CPU speed, a PCI Express interface, a Real Time Clock (RTC) and a power button and runs significantly cooler than Pi 4.
SCREENSHOT_ALLOWED_HOSTS=example.com node server.js
Configure the MCP client to start this script using its local server configuration. The process should write protocol messages to stdout only; send diagnostics to stderr so ordinary log output cannot corrupt the stdio transport. The code launches a browser for each capture to keep the example’s lifecycle easy to reason about. In production, browser startup and process reuse can be optimized, but keep browser contexts isolated across requests and tenants.
Protect the browser worker and the network boundary
A screenshot worker fetches a URL chosen by a caller, so it can become a server-side request forgery (SSRF) path into internal systems. An exact hostname allowlist is the simplest policy when the service only needs to capture known sites. If callers can submit arbitrary public URLs, implement network-level egress controls instead of relying on a one-time URL parse.
- Permit only the schemes you intend to support; reject
file:,data:, and other non-web schemes. - Resolve hostnames before connection and block loopback, link-local, and private address ranges unless a specific use case requires them. Guard against redirects to disallowed destinations as well.
- Use an egress proxy or equivalent network policy to enforce the destination rule at connection time, including after DNS resolution. Limit redirect count.
- Apply request deadlines, byte caps, and per-tenant concurrency and browser-process limits. Stop browser work when its request is cancelled or times out.
- Use isolated browser contexts; do not share a persistent authenticated context between tenants. Keep credentials in a secret manager rather than page content.
- Scrub logs. Avoid recording cookies, authorization headers, page text, signed links, or other secrets. Secret redaction is a convenience, not a security boundary.
Do not expose an arbitrary JavaScript execution tool to public or otherwise untrusted MCP clients. Playwright’s documentation warns: “This tool runs arbitrary JavaScript in the Playwright server process and is RCE-equivalent — only enable it for trusted MCP clients.” If an internal workflow genuinely needs code execution, put it behind separate authentication and run it in a disposable worker; do not add it to the public screenshot contract.
Run the service remotely and scale it safely
For clients connecting over a network, use the standalone HTTP server on its documented HTTP port and expose the /mcp endpoint. Put authentication and authorization at the edge, then apply per-tenant quotas inside the worker layer. Authentication alone does not protect the browser: the tool still needs URL policy, time and output limits, and isolation.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Rank #4
- All-in-One Complete Kit: This SANOOV RPi 5 bundle comes with Raspberry Pi 5 4GB RAM single board, active cooler, durable ABS case and screwdriver. No extra parts needed, ready to use right out of the box for beginners and hobbyists
- Powerful Single Board Computer: Equipped with 4GB RAM and high-performance processor, delivers fast running speed for 4K playback, AI projects, programming and daily computing tasks. SANOOV for raspberry pi 5 4GB is equipped with broadcom 64 quad-core Arm Cortex A76 processor with gigabit ethernet and upgraded with IEEE 802.11ac Wi-Fi, Bluetooth 5.0 dual-band 2.4Ghz and 5Ghz and Power Over Ethernet (POE). Upgrading delivers 2-3 x speed vs Pi 4, redefining the experience
- Efficient Active Cooler: Effectively lowers operating temperature and prevents performance throttling. Runs quietly even under long-time heavy load, ensures stable operation all day long. SANOOV RPi 5 4GB kit offer an active cooler, which combines an aluminium heatsink with a high-performance PWM fan. Active cooler is fully compatible with the Pi OS, which can effectively reduce the temperature of RPi5 and ensure its good performance during long-term high load operation
- Sturdy ABS Protective Case: Well-fitted for Raspberry Pi 5 board, can be secured with 4 screws to effectively protect the Pi 5 motherboard from damage, reserves full access to all ports and buttons. SANOOV uses ABS material to produce the case, which has a softer texture and feel. Meanwhile, SANOOV case adopts a layered design for easy disassembly and installation. (Tip: The Case cannot install M.2 HAT Add on Board and Solid State Drive!)
- Wide Application & Full Compatibility: Seamlessly compatible with official OS and mainstream peripheral accessories for Raspberry Pi 5. Whether you are a beginner, student, electronics hobbyist or professional developer, this all-in-one kit meets your diverse needs. It excels in IoT projects, robotics design, retro gaming devices, home media servers and other DIY creations. Backed by a large global community, you can easily find guides, technical support and shared projects online
Design the service as a request path with separable responsibilities: an MCP adapter accepts a validated tool call; a policy layer checks the destination and resource limits; a browser worker performs the capture; and an artifact layer decides whether to return bytes or store a short-lived result. This separation makes it easier to add worker queues or replace local storage without broadening the tool’s permissions.
The MCP announcement dated July 28, 2026 describes a stateless protocol core, authorization hardening, header-based routing, and cache metadata for list and read operations. Its release-candidate announcement specifies Mcp-Method and Mcp-Name headers for Streamable HTTP routing and explains that stateless servers can run behind ordinary round-robin load balancers. Treat these as protocol-revision-specific capabilities, not assumptions that apply to every client or server combination. Pin your MCP SDK and Playwright versions, record the negotiated MCP protocol revision, and test client compatibility before upgrades.
For larger captures, storing the artifact separately is often a better interface than trying to return an enormous inline image. Give artifacts a short retention period, restrict access to the requesting tenant, and return only the reference and metadata the caller needs. Remove temporary files after expiration or failed jobs. Avoid claiming a universal latency or throughput figure: capture time depends on the page, browser work, wait policy, image dimensions, and available resources.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Choose capture behavior deliberately
Playwright MCP supports Chrome, Firefox, WebKit, and Edge. The right browser set depends on the sites your users need to capture and your operating budget; validate each supported browser in your own deployment rather than assuming identical output or resource use. For a stable target, use an accessibility snapshot or DOM locator to identify the relevant element, then use a screenshot only when the caller actually needs a visual artifact.
Best Value
- Not including the Raspberry Pi 5 (8GB), the Crowpi advanced version comes with the Raspberry Pi 5
- ELECROW Black Case for the Raspberry Pi 5, CrowPi is equipped with a 9-inch HD touchscreen along with a camera; All the regular components used in DIY electronics are packed into the CrowPi development board, such as LCD, LED matrix, buzzer, light sensor, PIR sensor, ultrasonic sensor, IR sensor, etc
- Raspberry Pi Sensors: The Crowpi raspberry pi 5 programming kit is jam-packed with lots of buttons such as 19 different sensors in a tidy easy to use package; You don't have to wait and wire things
- Build Quality: Solid ABS shell and well made components in one place make it strong and convenient to travel
- Programming Lessons: This raspberry pi 5 learning kit ships with step by step instructions and provides 21 lessons to take you through identifying components reading code and running it in the terminal
Use an explicit, bounded readiness condition. Waiting for network idle can be unsuitable for pages with continuing background traffic; waiting only for DOM content may capture before client-rendered content or lazy images appear. Offer a small number of modes, document their trade-offs, and cap the overall deadline regardless of the selected mode. If full-page capture is enabled, set a pixel-count limit as well as width and height limits because a narrow page can still be exceptionally tall.
Test the failure paths before opening access
Build a repeatable test matrix before letting untrusted callers reach the worker. Include public pages, redirects, slow responses, JavaScript-rendered pages, very tall pages, element-only captures, every enabled output format, each supported browser, and concurrent requests. These are test cases to run in your environment, not a claim that any implementation has already passed them.
- Verify rejected URLs cannot reach loopback, link-local, or private destinations, including through redirects and DNS changes.
- Confirm a timeout actually terminates browser work and releases its context and worker capacity.
- Check that dimensions, full-page pixel count, response size, redirect count, and concurrency limits reject oversized or excessive work.
- Run concurrent requests for different tenants and verify that cookies, headers, and resulting page state never cross between them.
- Check malformed selectors, missing elements, unsupported formats, and invalid viewport values return actionable tool errors rather than browser stack traces or partial artifacts.
- Verify temporary artifacts expire, failed jobs leave no accessible output, and logs do not contain cookies, headers, page content, or secrets.
Troubleshooting common failures
| Symptom | Likely cause | What to check or change |
|---|---|---|
| The client cannot start the server | Node is below the documented Node.js 20 requirement for the official Playwright MCP setup, the package is missing, or the client points to the wrong working directory. | Check the Node version, dependencies, and launch command. For a local stdio server, confirm the script path and environment variables are available to the client process. |
| The tool rejects an expected URL | The scheme is not allowed, the hostname is absent from the allowlist, or the URL contains embedded credentials. | Use an approved HTTP(S) URL and add only the intended exact hostname to the server-side policy. Do not accept policy changes from the tool caller. |
| Navigation times out | The page is slow, unreachable from the worker, or the selected wait condition never settles. | Check network reachability and the chosen readiness condition. Keep a hard overall deadline; do not remove timeouts as a workaround. |
| The selector capture fails | The selector matches zero or multiple elements, or the page has not reached the state in which the element exists. | Use a stable selector that matches exactly one element and choose an appropriate readiness policy. Return a clear not-found error instead of silently taking a page-wide image. |
| The result is too large | Full-page capture, viewport dimensions, or image encoding produced bytes above the response cap. | Capture a smaller element or viewport, reduce the full-page pixel budget, or move the result to short-lived artifact storage. |
| Remote clients fail despite a healthy worker | Authentication, authorization, endpoint routing, or client/server protocol-revision mismatch. | Check edge credentials and the /mcp route, then compare the negotiated protocol revision and routing behavior with the client. Pin versions and test upgrades together. |
Or skip the browser setup
If you need screenshot capture rather than operating a browser worker, ScreenshotNeo is a website screenshot API and MCP server. One GET request takes a URL and returns a PNG, JPEG, WebP, or PDF. Its clean-shot steps accept cookie or consent banners and remove more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and responses identify the page verdict and billing status in headers. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
Here is the one-call cURL example; see the ScreenshotNeo API documentation for request options and response details:
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Equivalent Python and Node.js requests are:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
- Cookie banners, popups, and chat widgets are removed before the shot.
- Bot checks, blank pages, and failed loads are never billed.
- An MCP server lets AI agents take screenshots.
- The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.
Sign up for ScreenshotNeo and start with 1,000 free screenshots a month, no card required.
Frequently Asked Questions
Should a screenshot tool return image content or a URL?
Return MCP image content when it fits the response budget; use a short-lived artifact reference for larger output.
Should the service expose browser interaction as well as screenshots?
Only add interaction capabilities when a concrete use case requires them, and keep arbitrary code execution separate from the screenshot surface.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




