pursr-mcp
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@pursr-mcprun an accessibility audit on https://example.com"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Why pursr?
Most teams need four separate tools to do visual QA: a screenshot CLI, a regression diff runner, an accessibility auditor, and a way to share captures with an AI assistant. pursr is all four - built as a single Node.js package with:
A unified CLI (
pursr) for every capture, diff, sweep, and audit.An MCP stdio server (
pursr-mcp) so Claude Code, Cursor, and Continue can take screenshots, run sweeps, and inspect prior captures as MCP resources.A library with 30+ named exports and 16 subpath modules, so you can embed it in your own tooling.
A plugin system for custom viewports, sweep ops, and capture hooks.
Zero browser bundled - drives your system Chrome via Playwright. No 200 MB Chromium download.
Related MCP server: PagePixels Screenshots MCP Server
Install
npm install pursr
npm install --save-dev playwright-core # peer dep - bring your own ChromeThen verify:
pursr viewports # list 10+ registered viewport presets
pursr probe https://example.com # health check30 seconds
# 1. Capture a screenshot with overlays
pursr shoot https://example.com shot.png \
--preset desktop-1280 --grid --grid-tile 64
# 2. Save it as a visual baseline
pursr baseline save myapp shot.png home --url https://example.com
# 3. Next time you run, compare against the baseline
pursr diff https://example.com \
~/.pursor/baselines/myapp/<id>/home.png \
diff.png
# 4. Or: run a batched sweep + a11y audit + parallel workers
pursr sweep ./plan.json # see plans/ for an exampleFeatures
Feature | Description | CLI flag |
Multi-viewport capture | 10+ presets (mobile, tablet, desktop, ultrawide) |
|
Layered states | entity / terrain / hud / ui isolation |
|
Animation freeze | pause CSS/JS animations for stable frames |
|
Cursor overlay | pointer / grab / grabbing / crosshair |
|
Grid overlay | spacing guides, custom color + tile size |
|
Camera control | zoom + pan via mouse wheel/drag |
|
Frame timeline | N captures at intervalMs for animations |
|
Hover capture | text=/role=/aria=/placeholder= matchers |
|
Pixel diff |
|
|
Visual baselines | save / approve / diff with stable IDs |
|
Parallel sweep | opt-in worker pool across independent steps |
|
Accessibility audit | axe-core WCAG 2.1 AA + highlighted screenshot |
|
DOM snapshot | serialized HTML + computed styles + selector map |
|
Sweep plans | JSON-driven batch with per-step ops |
|
HTML report | dark-themed grid of every capture + meta | auto-generated |
CI output | JUnit XML, GitHub Actions annotations, Markdown | written on every sweep |
Auto-heal selectors | fallback chain + named matchers |
|
HAR capture | HAR 1.2 spec, written next to your shot |
|
Auth state | Playwright storageState, reuse logged-in sessions |
|
Plugins | custom viewports, sweep ops, before/after hooks |
|
MCP server | 7 tools + resources/list & resources/read for Claude/Cursor |
|
CLI
# Health check
pursr probe https://example.com
# Screenshot (simple)
pursr shot https://example.com ./out/shot.png
# Rich capture: viewport preset + cursor + grid
pursr shoot https://example.com \
--preset desktop-1280 \
--cursor crosshair \
--grid --grid-tile 64
# Isolate a layer
pursr layer https://example.com entity
# Animation timeline
pursr frames https://example.com 8 200 ./frames/
# Hover an element
pursr hover https://example.com "text=Login"
# Pixel diff vs reference
pursr diff https://example.com ./ref.png ./out/diff.png
# Batched plan
pursr sweep ./plan.json
# Accessibility audit
pursr audit https://example.com --tags wcag2a,wcag2aa
# DOM + selector map snapshot
pursr dom https://example.com
# HAR capture during a shoot
pursr shoot https://example.com shot.png --har ./req.har.json
# Auth state reuse
pursr shoot https://my.app/dashboard shot.png \
--auth-state admin --auth-project myapp
# Visual baselines
pursr baseline save myapp shot.png home --url https://example.com
pursr baseline list myapp
pursr baseline approve myapp ./new.png home --url https://example.com
# Plan validation
pursr validate ./plan.jsonSubcommands
Subcommand | Purpose |
| Health check (HTTP status, page title) |
| Viewport / full-page screenshot |
| Execute JS in the page, return result |
| Interaction primitives |
| Pixel-level diff vs a reference PNG |
| List all registered viewport presets |
| Rich capture (overlays, freeze, camera, plugins) |
| Capture one isolated layer (entity/hud/ui/terrain) |
| N-frame animation timeline at interval |
| Hover state capture |
| Batched capture plan -> HTML report + CI output |
| axe-core WCAG accessibility audit + highlighted screenshot |
| Serialized DOM + CSS selectors + XPath + bounding rects |
| Capture once per preset in parallel (3-wide pool) |
| save / list / approve / show visual baselines |
| save / load / list / delete Playwright storageState |
| Validate a sweep plan JSON without running it |
MCP Server
pursr-mcp exposes every capability as MCP tools over stdio - works with Claude Code, Cursor, Continue, and any MCP host.
npx pursr-mcp
# or with verbose logging:
npx pursr-mcp --verboseExposed Tools
Tool | Description |
| Rich screenshot capture (viewport, grid, layer, cursor, camera, animation freeze, HAR) |
| Pixel-diff a URL against a reference PNG |
| Execute a batch sweep plan |
| Capture an N-frame animation timeline |
| Health-check a URL |
| axe-core WCAG audit + highlighted screenshot |
| Full DOM + selector map snapshot |
Exposed Resources
URI | Description |
`pursr://shoot/<url | preset>` |
| Last sweep summary JSON (application/json) |
Resources are persisted to ~/.pursor/mcp/mcp-index.json (override with PURSOR_MCP_STATE).
Visual Regression Baselines
pursr baseline save myapp ./out/shoot.png home --url https://my.app
pursr baseline approve myapp ./out/shoot.png home --url https://my.app
pursr baseline list myapp
pursr baseline show myapp home --url https://my.appBaselines live under ~/.pursor/baselines/<project>/<id>/<step>.png + manifest.json. Override with PURSOR_BASELINES_DIR. The id is a 16-char SHA1 prefix of url|viewport|flags so re-running a sweep maps to the same slot deterministically.
import { diffKey, saveBaseline, loadBaseline } from "pursr/baseline";
const id = diffKey({ url: "https://my.app", viewport: { width: 1280, height: 800, dpr: 1 }, flags: { preset: "desktop-1280" } });
saveBaseline({ project: "myapp", id, step: "home", png: "./shot.png", meta: { url: "https://my.app" } });Sweep Plan Validation
pursr validate ./plan.json
# { "valid": false, "errors": ["steps[2].frames.count: must be a number between 1 and 120"] }Catches: empty steps, unknown ops, out-of-range numbers, duplicate names, missing required fields. pursr sweep runs the same validator before executing - fail-fast.
{
"name": "homepage-matrix",
"base": "https://example.com",
"parallel": 4,
"steps": [
{ "name": "baseline", "shoot": { "preset": "desktop-1280" } },
{ "name": "grid-64", "shoot": { "preset": "desktop-1280", "grid": true, "grid-tile": 64 } },
{ "name": "tablet", "shoot": { "preset": "tablet-768" } },
{ "name": "mobile", "shoot": { "preset": "mobile-375" } },
{ "name": "hover-cta", "hover": { "selector": ["text=Get started", "a.btn-primary"] } },
{ "name": "audit", "audit": { "tags": "wcag2a,wcag2aa" } },
{ "name": "diff", "diff": { "ref": "baseline" } }
]
}HAR Capture
pursr shoot https://example.com shot.png --har ./out/req.har.jsonimport { startHarCapture, stopHarCapture, writeHar } from "pursr/har";
const state = await startHarCapture(page);
await page.goto(url);
const har = stopHarCapture(page);
await writeHar(har, "./out/req.har.json");Output is HAR 1.2 spec - pipe to har-cli, perf-tools, or any visualizer.
Auth State
pursr auth save myapp admin --from ./playwright-state.json
pursr shoot https://my.app/dashboard shot.png --auth-state admin --auth-project myapp
pursr auth list myapp
pursr auth load myapp admin --out ./round-trip.json
pursr auth delete myapp adminStates live in ~/.pursor/auth/<project>/<name>.json (override with PURSOR_AUTH_DIR). The on-disk format is the standard Playwright storageState shape: { cookies, origins }.
Parallel Sweep
Add parallel: N to your plan to run steps concurrently in a worker pool:
{
"name": "matrix",
"base": "https://my.app",
"parallel": 4,
"steps": [
{ "name": "home", "shoot": { "preset": "desktop-1280" } },
{ "name": "pricing", "shoot": { "preset": "desktop-1280" } },
{ "name": "docs", "shoot": { "preset": "desktop-1280" } }
]
}Steps run in a shared browser context; results are still ordered by index in the summary. Defaults to serial (parallel: 1) - opt in only when steps are independent.
Accessibility Audit
pursr audit https://example.com --tags wcag2a,wcag2aa
# Writes: audit.json, audit-summary.md, audit-highlighted.pngInjects axe-core, runs a configurable tag set (wcag2a, wcag2aa, wcag21a, wcag21aa, best-practice), and overlays a red outline on every violating node with the rule id as a label. The summary Markdown includes per-rule failure snippets.
DOM Snapshot
pursr dom https://example.com
# Writes: dom-snapshot-<ts>.dom.jsonCaptures serialized HTML, computed CSS for every visible element, and a selector map (id, role, accessible name, text, xpath, css selector, viewport-relative rect). Great for regression diffing without re-running a browser.
CI Output
Every sweep writes three sidecar artifacts alongside sweep.json:
sweep.junit.xml- JUnit XML for Jenkins / GitLab / CircleCIsweep.github.json- GitHub Actions annotation filesweep.md- Human-readable Markdown summary with diffs + failures
Library API
import {
runProbe, runShot, runShoot, runSweep, runDiff, runAudit,
captureDomSnapshot, resolveHealedSelector,
saveBaseline, diffKey,
startHarCapture, stopHarCapture, writeHar,
loadAuthState,
PursorMCPServer, loadMcpConfig,
validateSweepPlan,
listResources, readResource,
listViewports, resolveViewport, VIEWPORTS,
loadPlugins, registerPlugin, getSweepOp,
VERSION,
} from "pursr";Subpath exports
import { resolveLocator } from "pursr/selector";
import { launch } from "pursr/runway";
import { parseFlags, asNum } from "pursr/util";
import { overlayGrid } from "pursr/overlays";
import { captureDomSnapshot } from "pursr/dom-snapshot";
import { runAudit } from "pursr/plugin-audit";
import { resolveHealedSelector } from "pursr/selector-heal";
import { writeCiOutput } from "pursr/ci-output";
import { diffKey, saveBaseline, loadBaseline } from "pursr/baseline";
import { validateSweepPlan } from "pursr/sweep-schema";
import { startHarCapture, stopHarCapture } from "pursr/har";
import { saveAuthState, loadAuthState } from "pursr/auth";
import { listResources, readResource } from "pursr/mcp-resources";
import { PursorMCPServer } from "pursr/mcp";Plugins
A plugin is a plain ES module that exports a default object:
// plugins/my-plugin.js
export default {
name: "my-plugin",
viewport: { "my-laptop": { width: 1440, height: 900, dpr: 2, label: "MBP 14" } },
sweepOp: {
lighthouse: async (ctx, opts) => { /* ... */ },
},
beforeShoot: async (ctx) => { /* mutate ctx.flags / ctx.viewport */ },
afterShoot: async (ctx, meta) => { /* augment sidecar */ },
flagHelp: { "my-flag": "what it does" },
};Plugins are auto-loaded from plugins/ (built-in) or via --plugin <path>.
Architecture
src/
index.js - public library entry
mcp.js - MCP stdio server (JSON-RPC 2.0)
shoot.js - runShoot (overlays + camera + frame-stable)
sweep.js - runSweep (validated, parallel pool)
diff.js - pixelmatch wrapper
plugin-audit.js - axe-core injection + highlighted screenshot
dom-snapshot.js - full DOM + CSSOM + selector map
selector-heal.js - auto-heal chain resolver
ci-output.js - JUnit / GitHub / Markdown
baseline.js - visual regression storage
har.js - HAR 1.2 network capture
auth.js - Playwright storageState
sweep-schema.js - plan validator
mcp-resources.js - MCP resources adapter
overlays.js - page-side CSS overlays + camera
runway.js - Playwright launcher + system-Chrome detector
viewport.js - built-in viewport presets
selector.js - text=/role=/aria=/placeholder= parser
plugin.js - plugin registry + hook runner
util.js - flags, args, hashing, HTML escape, renderSweepHtml
every-viewport.js - one shot per preset in parallel
frames.js, hover.js, shot.js, eval.js, probe.js, interact.jsDevelopment
git clone https://github.com/0xheycat/pursr
cd pursr
npm install
npm install --save-dev playwright-core
npm testnpm test runs 53 unit + integration tests (Node's built-in test runner, zero test deps). Coverage includes: viewport resolution, flag parsing, selector parsing, HTML escaping, hashing, baseline storage, sweep-plan validation, MCP resources, HAR 1.2 shape, auth state, and end-to-end CLI smoke tests.
src/ - 25 modules
test/ - 53 tests, 0 failures
plugins/ - 2 built-in plugins, auto-loadedRoadmap
Visual baselines (save / approve / diff)
Sweep plan schema validation
MCP resources (browse past captures from your AI host)
HAR 1.2 capture
Auth state (Playwright storageState)
Parallel sweep workers
Watch mode (
pursr watch <url>)Component-level snapshot (
pursr snap <selector>)PDF report export
Cloud output adapters (S3 / GCS)
AI diff summary (vision model)
Watch Mode (v0.5.0)
# Re-shoot every time a CSS or HTML file changes
pursr watch https://my.app --on src/**/*.css --on src/**/*.html
# Re-run a sweep plan on file change
pursr watch --plan ./plan.json --on src/**/*.{css,html}
# Default (no --on) = watch everything in cwd
pursr watch https://my.appGlob patterns: * (one path segment), ** (any depth), ? (one char), backslash-X (literal X). Debounce is 300ms by default.
Component Snapshots (v0.5.0)
# Capture one screenshot per matched element
pursr snap https://my.app a.btn --out ./snaps --max 20
# Use auto-heal selector chain
pursr snap https://my.app "text=Sign up" --out ./snaps
# Promote to baselines in one command
pursr snap https://my.app article.product --baseline myappEach capture is clipped precisely to the elements bounding box (even when scrolled offscreen), labelled with aria-label / text / tag, and written to ./snaps/-.png + snap.json summary.
License
MIT (c) 2026 - 0xheycat
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/0xheycat/pursr'
If you have feedback or need assistance with the MCP directory API, please join our Discord server