How to Use an MCP Server to Capture Website Screenshots in GitHub Actions
Connect an MCP client to Playwright MCP in GitHub Actions, capture a website screenshot, and upload it as an artifact.
To capture a website screenshot with an MCP server in GitHub Actions, the workflow must run both an MCP server and a client that calls its screenshot tool. The example below uses Playwright MCP: Node.js starts the server over standard input/output, calls browser_navigate and browser_take_screenshot, then GitHub Actions uploads the saved PNG as an artifact.
An MCP server exposes tools; it does not independently run a workflow or save a screenshot where GitHub can retain it. The client owns the server process and must choose a predictable output path. Playwright MCP is designed for browser automation through MCP, with structured accessibility snapshots for interaction and a separate screenshot tool for visual output. Playwright MCP overview
1. Decide whether MCP belongs in this job
Use MCP when an MCP client or agent needs to operate the browser through tools. For a scheduled, fixed screenshot with no agent involved, direct Playwright code in the workflow has fewer moving parts; Playwright documents installing browsers and running tests in CI. The two approaches can produce screenshots, but only the MCP route exercises an MCP client/server connection. Playwright CI guide
| Approach | Choose it when | Workflow responsibility |
|---|---|---|
| MCP client plus Playwright MCP | An agent or another MCP client must use browser tools. | Start the server, connect the client, invoke navigation and capture tools, and write the output to the artifact directory. |
| Direct Playwright | The workflow only needs deterministic browser automation. | Install project dependencies and browser binaries, run a script or test, then upload its output. |
2. Add a small MCP screenshot client
Create scripts/capture.mjs. This Node.js client launches Playwright MCP as a child process over stdio, calls its tools, and writes the PNG to screenshots/homepage.png. It checks tool results and always closes the client connection.
import { mkdir } from 'node:fs/promises';
import { Client } from '@modelcontextprotocol/client';
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';
const target = process.env.SCREENSHOT_URL ?? 'https://example.com';
const output = 'screenshots/homepage.png';
await mkdir('screenshots', { recursive: true });
const client = new Client({ name: 'github-actions-screenshot', version: '1.0.0' });
const transport = new StdioClientTransport({
command: 'npx',
args: ['--yes', '@playwright/mcp@latest', '--headless']
});
try {
await client.connect(transport);
const navigation = await client.callTool({
name: 'browser_navigate',
arguments: { url: target }
});
if (navigation.isError) {
throw new Error(`Navigation failed: ${JSON.stringify(navigation.content)}`);
}
const capture = await client.callTool({
name: 'browser_take_screenshot',
arguments: { filename: output, type: 'png', fullPage: true, scale: 'css' }
});
if (capture.isError) {
throw new Error(`Screenshot failed: ${JSON.stringify(capture.content)}`);
}
console.log(`Screenshot saved to ${output}`);
} finally {
await client.close();
}
The MCP TypeScript SDK documents this client and stdio transport pattern: the transport launches the server process and the client invokes its tools. MCP TypeScript SDK client connection guide. Playwright MCP documents --headless, navigation, and screenshot tool options. Playwright MCP installation · Screenshot tool options
For reproducible builds, replace floating @latest with a reviewed, pinned package version and pin third-party actions to reviewed versions or full commit SHAs according to your repository policy. The quick-start configuration documents @latest; it does not guarantee that a future package release has identical behavior.
3. Run it in GitHub Actions and retain the image
Add .github/workflows/capture.yml. Set SCREENSHOT_URL to the site you own or are authorized to capture. The workflow requests no repository write permissions because this job only reads its checkout and uploads an artifact.
name: Capture website screenshot
on:
workflow_dispatch:
push:
branches: [main]
permissions:
contents: read
jobs:
capture:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install MCP client SDK
run: npm install --no-save @modelcontextprotocol/client
- name: Capture screenshot with Playwright MCP
run: node scripts/capture.mjs
env:
SCREENSHOT_URL: https://example.com
- name: Upload screenshot
uses: actions/upload-artifact@v4
with:
name: website-screenshots
path: screenshots/
if-no-files-found: error
retention-days: 7
This is a complete workflow shape, but the package install shown is intentionally convenient for a small standalone script. For a maintained project, record the client dependency in package.json, commit the lockfile, and use npm ci. Review and pin the action versions according to your update process; update the sample versions when your repository adopts newer reviewed releases. GitHub artifacts can retain a file or directory for a configured period, subject to repository, organization, or enterprise limits. GitHub artifact documentation
- Commit the script and workflow.
- Run the workflow from the Actions tab with
workflow_dispatch, or push tomain. - Open the completed run and download the
website-screenshotsartifact. - For scheduled captures, add a cron-based
scheduletrigger and account for the target site’s timezone and content update cadence.
4. Configure the screenshot you need
The example sets a stable filename, PNG format, full-page capture, and CSS-pixel scale. The screenshot tool also accepts an element target, other supported image types, and device-pixel scale. A full-page screenshot cannot be combined with an element target. Playwright screenshot parameters
| Need | Tool arguments | Notes |
|---|---|---|
| Viewport only | { filename: 'screenshots/home.png' } |
Default behavior captures the visible viewport. |
| Full document | { filename: 'screenshots/home.png', fullPage: true } |
Do not also set target. |
| One element | { filename: 'screenshots/card.png', target: 'main article' } |
Use a CSS selector supported by the tool or an element reference from browser interaction. |
| Image format | type: 'png', 'jpeg', or 'webp' |
If omitted, the extension can determine the type; PNG is a practical default for artifact review. |
| Pixel scale | scale: 'css' or 'device' |
CSS is the default; device scale produces a higher resolution image. |
| Predictable output | filename: 'screenshots/homepage.png' |
Without a filename, the server generates a timestamped name in its output directory, which complicates stable artifact paths. |
For a selector-based capture, navigate first and then pass the target selector to browser_take_screenshot. For interactive pages, add the appropriate browser tool calls before the screenshot. MCP tool names and schemas can vary by server version, so inspect the connected server’s tool list if a tool call reports an unknown name or invalid arguments.
5. Upload and permission choices
- Artifact path: Match
pathto the directory used by the screenshot tool.if-no-files-found: errormakes a missing image fail the job instead of silently producing an empty artifact. - Retention: Set
retention-daysto the shortest useful period; repository policy can impose a lower maximum. - Token permissions: Keep
GITHUB_TOKENscopes minimal. When workflow permissions are explicitly set, unlisted scopes become none. This screenshot-only example needs checkout read access, not broad write access. GitHub token permissions - Pull requests from forks: GitHub ordinarily restricts write permissions for fork-originated pull request workflows. Avoid designing screenshot capture around a write token unless the workflow actually requires one, and do not expose secrets to untrusted contributions.
- URL input: A fixed URL is simplest. If accepting a workflow input, validate allowed hosts before navigating; a workflow that can reach arbitrary URLs can be abused to probe internal or sensitive endpoints.
6. When direct Playwright is simpler
If no MCP client needs to control the browser, use Playwright directly. The official CI guide demonstrates installing dependencies and browser binaries, running Playwright, and archiving an artifact. This compact script writes a screenshot without involving MCP:
import { chromium } from '@playwright/test';
const browser = await chromium.launch();
try {
const page = await browser.newPage();
await page.goto(process.env.SCREENSHOT_URL ?? 'https://example.com', {
waitUntil: 'networkidle',
timeout: 30_000
});
await page.screenshot({ path: 'screenshots/homepage.png', fullPage: true });
} finally {
await browser.close();
}
Install @playwright/test as a project dependency, run npx playwright install --with-deps chromium, and upload screenshots/ with the artifact step above. A direct script does not invoke Playwright MCP tools. Use the MCP workflow when tool interoperability is part of the requirement; choose direct Playwright for a fixed browser task. Playwright CI installation pattern
Or skip the browser setup
For a one-request screenshot rather than a browser runtime and artifact pipeline, ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. Its API accepts a URL and returns an image or PDF. See the ScreenshotNeo API documentation for request parameters.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com \
-o shot.webp
Cookie banners are accepted and removed before the capture, along with known consent platforms, newsletter popups, and chat widgets; those steps can be disabled. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server provides screenshot, page-info, and PDF tools for AI clients. The free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots.
Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.
Performance, reliability, and cost
- Startup work: The MCP route starts a Node process, initializes an MCP connection, launches a browser, navigates, captures, and uploads. Browser downloads on first use, so a clean runner may spend time provisioning. Do not assume caching browser binaries improves CI time; Playwright says restore time can be comparable to downloading them and Linux system dependencies are not cacheable. Playwright browser caching guidance
- Control variability: Screenshots can change with dynamic content, fonts, network responses, ads, consent overlays, and viewport. Use a stable URL, fixed filename, explicit capture mode, and a readiness condition appropriate to the page. Prefer waiting for a specific selector when the page’s meaningful content appears after navigation; an arbitrary sleep adds delay without guaranteeing readiness.
- Timeouts: The workflow timeout is a final job bound, not a page-load timeout. Choose a navigation timeout based on the site and fail visibly when it is exceeded. Avoid retry loops that create uncontrolled load on the target site.
- Runner cost: GitHub Actions usage and artifact storage depend on your GitHub plan and configuration; this dossier establishes no specific price. Keep artifacts short-lived and avoid capturing on every event if the output is not needed.
- Screenshot service cost: ScreenshotNeo’s stated tiers are Free 1,000/month, Starter $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000. Yearly billing gives two months free; every feature is on every plan. Choose based on required volume and workflow needs.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
browser_navigate is unknown |
The server version or tool set differs from the expected one, or the wrong process was launched. | Inspect the server’s available tools and confirm the MCP server package and client are connected. Pin a reviewed version when stable tool names matter. |
| Invalid argument or screenshot tool error | Parameter spelling, value, or combination is unsupported; for example, full-page capture and element targeting are mutually exclusive. | Check the screenshot tool parameter table and remove incompatible arguments. Screenshot tool reference |
| Workflow says no files were found | The tool saved to its default output directory or a different filename/path. | Set filename explicitly, ensure the directory exists, and make artifact path match the output location. |
| Browser launch fails in CI | Browser binary or system dependencies are missing, or the server is running headed without a display. | Use the documented --headless option and allow the server to download its browser on first use. For direct Playwright, install browsers and dependencies using the CI guide. |
| Screenshot is blank or incomplete | Navigation returned before app content rendered, the target is below the fold, or the page depends on delayed content. | Wait for a meaningful selector or page state before capture; use fullPage: true when content below the viewport is needed. |
| Capture is inconsistent across runs | Dynamic content, responsive layout, asynchronous widgets, or changing assets differ between runs. | Keep URL and viewport stable, choose a deliberate readiness condition, and avoid comparing captures that include inherently changing content. |
| Client hangs or exits before capture | The process may have failed to initialize, the workflow timed out, or the client did not close cleanly. | Check preceding logs, retain a bounded job timeout, and use finally to close the client as in the example. |
| Fork pull request cannot write | GitHub restricts write-token permissions on fork-originated pull requests. | Keep screenshot jobs read-only where possible; do not depend on write access for artifact upload. |
FAQ
Does the MCP server upload the screenshot to GitHub?
No. It writes a local file in the runner workspace. A separate artifact upload step preserves it after the job ends.
Can I capture a page without an AI model?
Yes. A regular MCP client program can call the server tools, as the Node script does. An LLM is not a protocol requirement.
Can I use a screenshot filename generated by the server?
Yes, but a fixed filename is easier to upload, locate, and consume in later workflow steps.
Can I use this for a visual regression test?
Yes, but this workflow only captures and stores an image. Comparison against a baseline and failure thresholds are separate steps you must implement.


