ScreenshotNeo

BlogAI agents

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.

By the ScreenshotNeo team4 October 20269 min read

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

  1. Commit the script and workflow.
  2. Run the workflow from the Actions tab with workflow_dispatch, or push to main.
  3. Open the completed run and download the website-screenshots artifact.
  4. For scheduled captures, add a cron-based schedule trigger 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 path to the directory used by the screenshot tool. if-no-files-found: error makes a missing image fail the job instead of silently producing an empty artifact.
  • Retention: Set retention-days to the shortest useful period; repository policy can impose a lower maximum.
  • Token permissions: Keep GITHUB_TOKEN scopes 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.