mcp-perfectpixel
MCP server for pixel-perfect verification, screenshots a live URL and diffs it against a static design image, returning grouped diff regions with severity scores and source tracing.
README
mcp-perfectpixel
The missing verification layer for AI design-to-code workflows.
mcp-perfectpixel is an MCP server that
screenshots a live URL and diffs it against a static design image (PNG/JPG),
returning grouped diff regions with severity scores — not raw pixel noise —
each traced to its DOM element, real source location, and a minimal patch
suggestion. Capture is deterministic (animations disabled, fonts fully
loaded, fixed locale/timezone), so re-runs are stable enough to reason about
pixel-by-pixel.
It is a verification tool, not a design tool: it does not read Figma files, does not generate code, and does not know what framework you use. It closes the loop the other MCP tools leave open — "did the final result actually match the design?"
Why this exists
Shipping pixel-perfect themes for BigCommerce, Shopify, WordPress and landing pages usually goes like this: the build itself is fast, but the final "does it match the design" pass is a slow, manual, zoom-and-compare chore — and it is exactly the step AI coding agents get wrong (wrong spacing, off-by-one colors, missing tokens).
mcp-perfectpixel automates that verification loop: screenshot the live URL,
diff against the design image, get grouped regions + source locations + minimal
patches, fix, and re-run until similarity: 1.0. The calling agent (Claude
Code, Cursor, DeepSeek Agent, Codex) applies the fixes — the server supplies
accurate, structured evidence and stops there.
Where it fits
Three MCP servers, three moments of the design-to-code loop — they complement, not compete:
| Figma MCP | Chrome DevTools MCP | mcp-perfectpixel | |
|---|---|---|---|
| Gives you | Structured design data — node tree, styles, variables, tokens, generated code | Live DOM / CSS / console / network debugging of the running page | Pixel-level verification — diff of the final render against the design image |
| Use it | Before writing code — what should I build, what are the exact styles? | During development — why is it behaving like this, fix runtime issues? | After implementing — does the final result actually match the design, pixel by pixel? |
| Answers | What's in the design? | What's happening on the page? | Did we nail the design? |
mcp-perfectpixel is deliberately not a Figma MCP competitor: it never
touches Figma. It takes the flat image Figma MCP can hand it (or any PNG/JPG)
and verifies the rendered result — the step neither of the other two covers.
Features
- Deterministic capture — headless Chromium with animations/transitions
disabled,
prefers-reduced-motionforced, all web fonts awaited (document.fonts.ready), fixeden-USlocale + UTC timezone, light scheme,deviceScaleFactor: 1. Two runs produce byte-identical screenshots. - Grouped diff regions — differing pixels are clustered and nearby clusters
merged, so you get "the button is wrong", not 4,000 scattered pixels. Each
region carries a bounding box, pixel count, color deltas, and a severity
score (
high/medium/low). - Region → source tracing — every region resolves to its DOM element and the
CSS rules styling it, each with a best-effort original
file:line:column(CSS source maps first, then gitignore-aware text search) and a confidence score. - Minimal patches — the smallest single-property change
(
file, line, property, current → suggested), preferring design tokens the project already defines (var(--color-success), not a hardcoded hex). Never a component rewrite. - Artifacts on disk — screenshot + highlighted diff image (PNG) written to an output dir and returned, so the agent can inspect them.
- Token-friendly output — typed
structuredContent(declared output schema), trimmed computed style, rounded floats; ~37% smaller payloads. - Works with any stack — tracing operates at the compiled-CSS layer + text search, so Liquid, Stencil, Twig, JSX, Blade, Razor or plain HTML all behave identically. No per-framework parsers.
Install & run
Requires Node.js ≥ 20 and a Chromium binary (install once):
npx playwright install chromium
Claude Desktop — claude_desktop_config.json
{
"mcpServers": {
"perfectpixel": {
"command": "npx",
"args": ["-y", "mcp-perfectpixel"]
}
}
}
Cursor — .cursor/mcp.json
{
"mcpServers": {
"perfectpixel": {
"command": "npx",
"args": ["-y", "mcp-perfectpixel"]
}
}
}
Codex CLI — ~/.codex/config.toml
[mcp_servers.mcp-perfectpixel]
command = "/path/to/node"
args = ["/path/to/mcp-perfectpixel/packages/server/dist/index.js"]
(Running from source: command is the absolute node path, args points at
the built server entry. Restart Codex after editing. repoRoot defaults to the
session's working directory — your project — so tracing and token lookup run
against the code you're editing.)
Try it locally (no client needed)
pnpm install
pnpm --filter @mcp-perfectpixel/core exec playwright install chromium
pnpm build
# one command: renders the fixture design, diffs the fixture page, prints everything
node examples/demo.mjs packages/server/test/fixtures/design.html \
"file://$PWD/packages/server/test/fixtures/page.html"
examples/demo.mjs calls the engine directly with your own design image/URL:
node examples/demo.mjs <design.png|design.html> <url> [repoRoot].
Tool reference
capture_and_diff
Screenshots url, diffs it against designImagePath, returns regions + artifacts.
| Argument | Type | Description |
|---|---|---|
url |
string (required) |
Live URL to screenshot — http(s) or file URL. |
designImagePath |
string (required) |
Design image (.png, .jpg, .jpeg) or an http(s) image URL (e.g. a Figma export link). |
viewport |
{width, height} |
CSS-pixel viewport. Defaults to the design image's dimensions. |
outputDir |
string |
Where to write artifacts. Defaults to a fresh temp dir. |
waitForSelector |
string |
CSS selector to wait for before screenshotting. |
waitMs |
number |
Extra settle time after load, in ms (≤ 60s). |
diffThreshold |
number (0–1) |
pixelmatch sensitivity. Smaller = more sensitive. Default 0.1. |
repoRoot |
string |
Codebase root for source tracing. Defaults to the server cwd (required in hosted mode). |
mode |
"local" | "hosted" |
Trust boundary: local (default) allows file:///local paths; hosted blocks them + private networks (SSRF guard). |
computedStyle |
"minimal" | "full" | "none" |
Computed-style verbosity per region. minimal (default) keeps color candidates + values differing from the parent. |
The tool declares an output schema: MCP clients receive typed
structuredContent (validated) plus the JSON text. Every call reports
trace.status (skipped/ok/partial/failed) and trace.warnings — issues
are never silently swallowed.
Example result (abridged):
{
"status": "diff",
"similarity": 0.9951,
"diffRatio": 0.0049,
"regions": [
{
"id": 1,
"x": 60,
"y": 130,
"width": 120,
"height": 36,
"pixelCount": 4120,
"coverage": 0.99,
"meanDelta": 0.52,
"score": 0.58,
"severity": "high",
"source": {
"element": {
"tag": "button",
"id": null,
"classes": ["btn-primary"],
"selector": "button.btn-primary",
"computedStyle": { "background-color": "rgb(220, 38, 38)" }
},
"rules": [
{
"selector": ".btn-primary",
"media": null,
"supports": null,
"container": null,
"applies": "yes",
"properties": ["background-color"],
"declared": { "background-color": "#dc2626" },
"source": {
"file": "src/styles/_buttons.scss",
"line": 42,
"column": 5,
"via": "source-map",
"gitignored": false
},
"confidence": "high"
}
],
"confidence": "high",
"patches": [
{
"file": "src/styles/_buttons.scss",
"line": 42,
"column": 5,
"property": "background-color",
"current": "#dc2626",
"suggested": "var(--color-success)",
"value": "#16a34a",
"token": {
"name": "--color-success",
"reference": "var(--color-success)",
"kind": "css-variable"
},
"confidence": "high"
}
],
"notes": []
}
}
],
"capture": {
"url": "https://example.com",
"viewport": { "width": 800, "height": 600 },
"viewportSource": "design",
"locale": "en-US",
"timezoneId": "UTC",
"reducedMotion": true,
"animationsDisabled": true,
"fontsWaited": true,
"durationMs": 1842
},
"artifacts": {
"screenshotPath": "/var/folders/.../example.com-screenshot.png",
"diffImagePath": "/var/folders/.../example.com-diff.png",
"designImagePath": "/repo/designs/home.png",
"designImageSource": "/repo/designs/home.png"
},
"trace": { "status": "ok", "warnings": [] },
"repoRoot": "/repo"
}
Severity: score = 0.6·meanDelta + 0.25·coverage + 0.15·min(1, areaRatio·10),
high ≥ 0.5, medium ≥ 0.2, low < 0.2.
How it works
- Capture — the URL is screenshotted deterministically (animations killed, fonts awaited, fixed locale/timezone).
- Diff — the screenshot is diffed against the design image (pixelmatch); differing pixels are clustered into connected regions, merged when close, and scored by severity.
- Trace — each region's element and its CSS rules are resolved to real source locations: CSS source maps first, then gitignore-aware text search, then plain DOM evidence — never a guessed file.
- Patch — the design color is sampled from the image at the region, the
cascade winner (specificity / order /
!important) is found, and the smallest change is suggested, preferring the project's own design tokens.
Source tracing order
- CSS source maps — the standard build-tool-agnostic mechanism (Sass,
Less, PostCSS, Tailwind, Webpack, Vite all emit them). Each rule's byte
offset maps through the source map to the original
file:line:column→confidence: "high". Works regardless of the templating language, because it operates at the compiled-CSS layer. - Gitignore-aware text search — the selector is searched across
repoRoot(nested.gitignores and negations respected,node_modulesnever searched). Non-ignored source →"medium"; matches only in gitignored (build) paths →"low"; matches in test/docs files are deprioritized. - DOM evidence only — if nothing resolves, the element + computed style
are returned as-is with
confidence: "low".
Minimal patches
For color diffs the server derives the design's intended value by sampling the design image at the region and emits one smallest-possible change, preferring tokens the project already defines — CSS custom properties, Tailwind configs, style-dictionary JSON:
{
"file": "src/styles/_buttons.scss",
"line": 42,
"column": 5,
"property": "background-color",
"current": "#dc2626",
"suggested": "var(--color-success)",
"value": "#16a34a",
"confidence": "high"
}
When a patch has no anchor (e.g. the culprit color is inherited from an
ancestor, or set by an inline style), the result explains it in notes[]
instead of guessing.
Responsive design (avoid hardcoded width/height)
A design image is a single-viewport raster — it cannot encode breakpoints,
auto-layout or fluid behavior. Copying pixel dimensions out of it into
width: 120px; height: 36px is the fastest way to break a real theme on other
viewports. mcp-perfectpixel is designed so this doesn't happen by accident:
- It never suggests width/height patches — patches are color-only
(
background-color,color, borders, outline). Layout is never "fixed" by the tool. capture.responsivereports the page's own breakpoints — the distinct@media/@containercondition counts across all stylesheets. Non-zero means the page is responsive, and any px dimensions in the output are viewport-specific.notes[]warns when it matters: if an element renders at fixed px dimensions while the page uses media/container queries, or when a diff is geometry-only (no color change), the region note says so and tells the agent to prefer fluid sizing (min/max-width, flex/grid, spacing tokens) and to re-run the capture at other viewports to verify.- The values are still accurate —
width/heightin the computed style are the real rendered values at the capture viewport; they are evidence, not instructions.
For responsive intent, pair this tool with Figma MCP's structured data (auto-layout, constraints, variables) — the raster verifies the pixels, the structured data informs the layout strategy.
Designs from Figma
mcp-perfectpixel works from flat images only — the official Figma Dev
Mode MCP is the perfect
bridge: it exports any frame/node to an image, and this server verifies the
final render against it. The agent orchestrates both; mcp-perfectpixel never
talks to Figma itself.
Workflow — "implement this design from Figma":
- Figma MCP — export the node (
get_image-style tool) → an image URL. - mcp-perfectpixel —
capture_and_diffwithdesignImagePath= that URL (fetched automatically),url= the live page,repoRoot= the codebase. - Apply the returned regions + patches, re-run until
similarity: 1.0.
Standalone export (no Figma MCP needed):
export FIGMA_TOKEN=figd_... # create at https://www.figma.com/developers/api#access-tokens
node examples/figma-export.mjs \
"https://www.figma.com/design/FILE_KEY/slug?node-id=1689-7871" -o /tmp/design.png
node examples/demo.mjs /tmp/design.png https://localhost:3000
Design philosophy
- Structured evidence, not framework knowledge. The server's job ends at regions + element + rules + confidence + patches. It never guesses what generated the HTML/CSS — the calling agent owns that.
- Determinism is a feature. Same page, same design, same bytes — which is what makes pixel diffing meaningful.
- Minimal, swappable core. The engine lives in
@mcp-perfectpixel/core(framework-agnostic, no MCP dependency), so future tooling can reuse it.
The boundary (what the server will never do)
- parse templates or Figma files — tracing works at the compiled-CSS layer;
- maintain per-framework parsers/adapters (Liquid, Stencil, ...) — at most an optional community plugin, never a core dependency;
- propose full component rewrites — output is always a single-property change;
- apply patches or edit files itself — it reports
file:line:column+current → suggested, the agent decides.
Hardening
- Cascade-correct patches — specificity, declaration order,
!important; duplicate selectors map to their own source positions. - Conditional CSS —
@mediaviamatchMedia(),@supportsviaCSS.supports(),@containerreported asapplies: "unknown"; pseudo-element rules never match the element. - Resource limits — viewport ≤ 16.7M px, design ≤ 50MB (stat before read), ≤ 50 regions, bounded candidate selectors, fetch timeouts, capped file scans.
- Trust boundary —
mode: "local"/"hosted"with SSRF +file://protection and an explicitrepoRootrequirement. - Session-aware stylesheets — fetched through the browser's request context, so cookies apply and the traced CSS matches what the page rendered.
- Honest tracing —
trace.status/warningsreport failures and truncations; text-search matches in tests/docs/generated files are deprioritized. - Token-friendly output — rounded floats, trimmed computed style, shared repo-walk cache with parallel reads (~37% smaller payloads, ~58% faster).
- Secret hygiene —
.env/.npmrcgitignored; CI runs Gitleaks, lint, build, tests and coverage; the publish workflow re-runs everything before releasing.
Roadmap
- [x] Goal 1 — Deterministic capture + pixel diff
- [x] Goal 2 — Trace diffs to real source (CSS source maps → gitignore-aware text search, with confidence scoring)
- [x] Goal 3 — Minimal patch output preferring the project's own tokens
- [x] Goal 4 — Structured context hand-off (no framework knowledge)
- [x] Goal 5 — OSS conventions + release pipeline (semver from
v0.1.0, publish-on-tag for both packages)
The first real release needs a v0.1.0 tag and the NPM_TOKEN secret — see
CONTRIBUTING.md.
Development
pnpm install
pnpm --filter @mcp-perfectpixel/core exec playwright install chromium
pnpm lint # eslint + prettier
pnpm build # type-checked compile of both packages
pnpm test # 104 unit + e2e tests through the MCP stdio protocol
pnpm coverage # vitest coverage (v8)
See CONTRIBUTING.md.
License
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.