Skip to main content
Glama

mcp-perfectpixel

npm version CI License: MIT

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.

Related MCP server: eyeballs

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-motion forced, all web fonts awaited (document.fonts.ready), fixed en-US locale + 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 = "npx"
args = ["-y", "mcp-perfectpixel"]

(Requires Node.js ≥ 20 and npx playwright install chromium on the machine running Codex. repoRoot defaults to the session's working directory — your project — so tracing and token lookup run against the code you're editing. Restart Codex after editing. To run from source instead, point command/args at your local node + packages/server/dist/index.js.)

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.

platform

"shopify" | "bigcommerce" | "html-tailwind" | "react" | "vue" | "auto"

Codebase type — narrows source-search priority globs and enables platform token scanning (SCSS $vars, Shopify {%- schema -%} settings). Default "auto": detected from repoRoot markers.

designContext

{ scale?, tokens?, nodes? }

Design metadata from Figma: export scale (1/2/3) to align the viewport with the frame; resolved tokens (matched before repo tokens); nodes bounding boxes to name each region's layer.

textRegionThreshold

number (0–1)

Extra lenient pixelmatch threshold for text-like regions (high color variance). Regions whose diff disappears under it are dropped as anti-aliasing noise. Default: unset (no filtering).

viewports

[{width, height}]

Multiple viewports for responsive verification. Captures run sequentially at each breakpoint and return per-viewport results + overall status + average similarity. Overrides viewport when both are given.

analyzeLayout

boolean

Enable advanced layout analysis (spacing, typography, alignment) for diff regions. Returns layoutAnalysis in the result. Default false.

validateResponsive

boolean

Enable responsive validation across viewports: re-captures each breakpoint and reports overlap/overflow/misalignment/missing-element issues as responsiveAnalysis. Default false.

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",
      "figmaNode": "Home/CTA/Button",
      "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"
            },
            "figmaToken": {
              "name": "Success/500",
              "value": "#16a34a",
              "kind": "color"
            },
            "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.

Platform presets

platform tells the tracer which codebase it is looking at so source search and token scanning are prioritized correctly instead of being generic:

Platform

What changes

"shopify"

{%- schema -%} color settings in .liquid files become design tokens (settings.color_primary), and Liquid files rank first in text search.

"bigcommerce"

SCSS $vars (e.g. $primary-color) in .scss files become design tokens, and Stencil templates/ is treated as source.

"html-tailwind"

Tailwind config files are scanned for tokens; plain HTML/CSS search order.

"react" / "vue"

Source-search ordering favors JSX/Vue SFC style blocks; .vue matches rank as vue-sfc-style.

"auto" (default)

Detected from repoRoot markers (.liquid → Shopify, stencil.conf.json → BigCommerce, package.json deps → React/Vue, tailwind.config.* → HTML+Tailwind).

You only need to set it when the project mixes markers or the detection is ambiguous.

Design context (from Figma)

The tool works from flat images, but the agent usually has richer design data available (via Figma MCP). Pass it through designContext — every field is optional and best-effort:

{
  "platform": "shopify",
  "designImagePath": "https://figma-export/.../frame@2x.png",
  "designContext": {
    "scale": 2,
    "tokens": [
      { "name": "Success/500", "value": "#16a34a", "kind": "color" },
      { "name": "Space/4", "value": "4px", "kind": "spacing" }
    ],
    "nodes": [{ "name": "Home/CTA/Button", "x": 60, "y": 130, "width": 120, "height": 36 }]
  }
}
  • scale (1/2/3) — the export scale of the design image relative to the Figma frame. The viewport is derived by dividing the image dimensions by the scale, so the capture matches the frame and region coordinates line up with Figma's node boxes. Without it, an @2x export would make the page capture twice as wide as the frame.

  • tokens — Figma-resolved variables/styles. They are treated as ground truth: when a token's color matches the design value, its name wins the patch suggestion (figmaToken in the output) and a repo token matching the same value is still reported alongside (token).

  • nodes — Figma node bounding boxes (design-image space). Each diff region is annotated with figmaNode, the name of the layer whose box covers the largest fraction of the region's bounding box (ties go to the tighter node). Text-named nodes (text|label|title|price|desc|heading|body) also feed the textRegionThreshold noise filter.

textRegionThreshold (anti-aliasing noise)

A design's text and the live page's text are rarely rendered identically — different fonts, weights, or hinting produce small anti-aliasing differences that show up as noisy regions. When set, textRegionThreshold acts as a second, more lenient pixelmatch threshold applied only to text-like regions (high color variance in the design crop, or an overlapping text-named Figma node): any such region whose diff disappears under it is dropped as AA noise. Non-text regions are never affected, and the count of dropped regions is reported in trace.warnings — best-effort, never silent.

{ "textRegionThreshold": 0.2 }

Multi-viewport capture & responsive validation

viewports turns one call into a full responsive check: the page is captured at every breakpoint and each result is returned (MultiViewportResult), with an overall status (match only when all viewports match) and averageSimilarity.

Combined with validateResponsive, each breakpoint is also checked for real layout problems — overlap, overflow, misalignment, missing-element — returned as responsiveAnalysis with per-viewport issues and breakpoint coverage statistics:

{
  "viewports": [
    { "width": 375, "height": 812 },
    { "width": 768, "height": 1024 }
  ],
  "validateResponsive": true
}

Layout analysis (spacing, typography, alignment)

analyzeLayout digs into each diff region that isn't a pure color problem: spacing mismatches (margin/padding/gap/alignment) and typography mismatches (font-family/size/weight/line-height/letter-spacing) are reported with expected vs actual, a pixel delta, the element selector, and a concrete suggestion — using Figma node/token names when designContext is available:

{
  "analyzeLayout": true,
  "designContext": { "tokens": [/* … */], "nodes": [/* … */] }
}

Both analyses are best-effort: failures surface in trace.warnings, never as a failed call.

How it works

  1. Capture — the URL is screenshotted deterministically (animations killed, fonts awaited, fixed locale/timezone).

  2. Diff — the screenshot is diffed against the design image (pixelmatch); differing pixels are clustered into connected regions, merged when close, and scored by severity.

  3. 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.

  4. 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

  1. 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:columnconfidence: "high". Works regardless of the templating language, because it operates at the compiled-CSS layer.

  2. Gitignore-aware text search — the selector is searched across repoRoot (nested .gitignores and negations respected, node_modules never searched). Non-ignored source → "medium"; matches only in gitignored (build) paths → "low"; matches in test/docs files are deprioritized.

  3. 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.responsive reports the page's own breakpoints — the distinct @media / @container condition 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 accuratewidth/height in 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":

  1. Figma MCP — export the node (get_image-style tool) → an image URL; optionally read the frame's variables/styles and node boxes.

  2. mcp-perfectpixelcapture_and_diff with designImagePath = that URL (fetched automatically), url = the live page, repoRoot = the codebase — and designContext (scale, tokens, nodes) when available so patches speak the project's own token names and regions carry Figma layer names (see Design context).

  3. 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

End-to-end workflow scriptexamples/figma-workflow.mjs exports a Figma node, reads the frame's variables and node boxes, and runs a full verification in one shot, passing scale/tokens/nodes as designContext so patches speak the project's own token names:

node examples/figma-workflow.mjs \
  "https://www.figma.com/design/FILE_KEY/slug?node-id=1689-7871" \
  https://localhost:3000 /path/to/repo

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.

Explicitly out of scope (no apply_patch tool, read-only/advisory by design):

  • No apply_patch tool — this server only reports evidence; applying changes is the calling agent's job (and its review loop).

  • No ignore regions — you cannot mark areas of the design as "dynamic content" (cookie banners, live clocks, carousels) to skip them. Filter with diffThreshold/textRegionThreshold or exclude the element before capture.

  • No auth headers — the capture sends no extra headers and sets no cookies (session-aware stylesheet fetching uses only the page's own context).

  • No pseudo-element / container-query runtime evaluation::before / ::after rules never match the element, and @container conditions report applies: "unknown" (there is no standards API to evaluate them per element).

Hardening

  • Cascade-correct patches — specificity, declaration order, !important; duplicate selectors map to their own source positions.

  • Conditional CSS@media via matchMedia(), @supports via CSS.supports(), @container reported as applies: "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 boundarymode: "local" / "hosted" with SSRF + file:// protection and an explicit repoRoot requirement.

  • Session-aware stylesheets — fetched through the browser's request context, so cookies apply and the traced CSS matches what the page rendered.

  • Honest tracingtrace.status/warnings report failures and truncations; text-search matches in tests/docs/generated files are deprioritized.

  • Design-context aware — Figma tokens win patch suggestions (ground truth), node boxes name each region (figmaNode), and text anti-aliasing noise is filterable via textRegionThreshold.

  • Token-friendly output — rounded floats, trimmed computed style, shared repo-walk cache with parallel reads (~37% smaller payloads, ~58% faster).

  • Secret hygiene.env/.npmrc gitignored; CI runs Gitleaks, lint, build, tests and coverage; the publish workflow re-runs everything before releasing.

Roadmap

  • Goal 1 — Deterministic capture + pixel diff

  • Goal 2 — Trace diffs to real source (CSS source maps → gitignore-aware text search, with confidence scoring)

  • Goal 3 — Minimal patch output preferring the project's own tokens

  • Goal 4 — Structured context hand-off (no framework knowledge)

  • Goal 5 — OSS conventions + release pipeline (semver from v0.1.0, publish-on-tag for both packages)

Releases are cut from git tags (v0.1.0, v0.1.1 published). The publish workflow uses npm Trusted Publishing (OIDC) with provenance — no NPM_TOKEN, no OTP — and smoke-tests the published package via npx before finishing. 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        # 138 unit + e2e tests through the MCP stdio protocol
pnpm coverage    # vitest coverage (v8)

See CONTRIBUTING.md.

License

MIT

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

No tool schema history has been recorded yet.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    MCP server for rendering responsive screenshots of URLs at multiple viewports. Enables agents to capture screenshots and detect visual issues like overflow, clipped elements, and missing alt text.
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    An MCP server that measures how faithfully one UI reproduces another, returning a score and actionable findings for improvement.
    1
    MIT

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/hiimbomb1999/mcp-perfectpixel'

If you have feedback or need assistance with the MCP directory API, please join our Discord server