MCP server · computer vision

Score how faithfully your UI clones the design.

A reference goes in, your build goes in. Out comes a score that rises as you get closer — and a punch-list of what to fix.

reference
Reference design: a purple-to-orange brand gradient
candidate
Candidate build: the same gradient with its hues drifted

The idea

A reference, a candidate, and a score that rises as they converge.

Every run returns an overall, five sub-scores, a cv_findings punch-list, and diagnostic images. The score is built to be actionable and monotonic — not a single "true" number.

Built for the loop

Give your agent one number to climb.

Point a /loop — or any goal-driven agent run — at overall. It's the target: the agent renders, compares, fixes the top finding, and repeats until it clears your threshold. Because the score is monotonic and deterministic, a rising number is real, repeatable progress — safe to set as the goal and to gate on.

the hill-climb loop
target = 95
while overall < target:                     # the goal
    render(candidate)                        # your build step
    r = compare_designs(ref, candidate, mode="screen")
    overall = r["overall"]                   # ← the number to climb
    fix(r["cv_findings"][0])                 # highest-severity miss first
  • Set the goal. Pick a target like overall ≥ 95, or gate a PR / CI check on it.
  • Work the punch-list. cv_findings is already ordered by severity — fix the top item each pass.
  • Trust the delta. Same inputs, same score — so a higher number is measured progress, not luck.
monotonic

A number that only goes up as you get closer.

Deterministic and monotonic — so an agent can hill-climb it: render, compare, fix, repeat.

five dimensions

Not one score. Five you can act on.

Layout, color, content, typography, spacing — scored separately, so you see which axis is off.

punch-list

A punch-list, not just a grade.

Findings name the exact problem — a hex that drifted, an element that's missing, with its box.

motion

A screenshot can't see motion. This can.

compare_motion reads two frame sequences — do they animate with the same energy and rhythm?

deterministic

Same inputs, same score. Every time.

Seeded k-means, fixed pipeline, nothing random. Safe to optimize against, safe to trust when it moves.

honest

It tells you where it's weak.

Calibrated against human labels — with the correlation numbers and the failure modes published.

What it measures

Everything that makes a clone feel faithful.

Layout

weight 0.35

SSIM on the aligned grayscale. Measures shape — the global shift is registered away first, so a 1-px offset doesn't tank it.

weakness grayscale; plateaus under heavy blur.

Color

weight 0.20

A seeded k-means palette in CIELAB, matched one-to-one by ΔE2000. Score = 100·exp(−ΔE/19), each shift named as hex.

weakness flat in a single-theme UI; ignores position.

Content

weight 0.15

A contrast-adaptive detector matches the reference's blocks to yours by IoU. Missing or extra blocks become findings.

weakness coarse; region boxes wobble.

Typography

weight 0.15

Text lines by morphology, compared on amount and scale. Not OCR, not font identity.

weakness misses a wrong font; null when no text.

Spacing

weight 0.15

Outer margins and vertical rhythm from major blocks, each within a tolerance.

weakness coarsest; null on sparse views.

Aggregate

geometric

The five combine as a weighted geometric mean — one weak axis drags the whole score down.

overall = exp( Σ wᵢ·ln(scoreᵢ) )

Try it

Play with the actual math.

These run the same formulas the worker does.

Weighted geometric mean

One low dimension pulls the overall down far harder than an average would.

weighted geometric mean
arithmetic mean

Color · ΔE2000 → score

Each palette pair scores 100·exp(−ΔE/19), ΔE by CIEDE2000. Pick a candidate color.

ΔE2000
color score

Install & register

Running in about a minute.

Needs Node ≥ 20, Python ≥ 3.10, and — for capture_frames — a local Chrome.

1 · clone & build
git clone https://github.com/codenameakshay/design-compare-mcp
cd design-compare-mcp
npm install
npm run build
2 · set up the Python vision worker
python3 -m venv worker/.venv
worker/.venv/bin/pip install numpy pillow scikit-image \
  opencv-python-headless scikit-learn scipy
npm run smoke   # ends with: ALL CHECKS PASSED ✅
3 · register with Claude Code
claude mcp add design-compare --scope user -- \
  node /abs/path/to/design-compare-mcp/dist/index.js

Registers four tools: ping, compare_designs, compare_motion, capture_frames.

Is it any good?

Calibrated against human judgment.

Scored against 26 components of a dark component library vs. its Flutter port, each rated 0–100 by eye. Correlation is Spearman.

−0.03default preset, out of the box
+0.44dark-ui preset — the honest working number
+0.54best possible weighting — a ceiling, overfit to 26 points

Per-dimension correlation, on that dataset

  • layout
    +0.33
  • typography
    +0.31
  • content
    +0.23
  • color
    +0.03
  • spacing
    −0.13

A decent layout + typography checker on this set — not a full oracle. Drop your own labeled pairs into calibration/ and re-run the tuner to calibrate for your UI.

Where it can mislead you

The motion ceiling. A static screenshot can't see animation — use compare_motion.
Single-theme color. One palette means no variance for ΔE. Don't weight color.
Typography ≠ font. Amount and scale, not font family or weight.
One-shot animations. A reveal that finishes before sampling reads as static.

FAQ

Frequently asked questions

What problem does this solve?

Cloning a UI is a loop: render, compare to the reference, fix the biggest gap, repeat. This replaces the eyeballing with a reproducible number and a specific punch-list.

What do I feed it?

Two local images — a reference and a candidate. widget mode for a tight crop, screen mode for a full view. For motion, two frame sequences.

How is the score computed?

Five dimensions measured independently, combined with a weighted geometric mean. Anything that doesn't apply returns null and is excluded.

Is the number accurate?

Useful, not authoritative — about +0.44 Spearman with human judgment on the calibration set. A first-pass gate and punch-list, not a replacement for a human's final call.

Can I calibrate it for my own UI?

Yes. Drop (reference, candidate, your 0–100 label) pairs into calibration/ and run scripts/calibrate.py.

Why TypeScript and Python?

TypeScript speaks MCP and drives headless-Chrome capture; a resident Python worker does the vision, so the heavy imports load once, not per call.

Is it safe to run?

It reads local paths and navigates to URLs you give it. Inputs are size-capped, metrics isolated, the worker respawns on crash. Set DESIGN_COMPARE_ALLOWED_ROOTS to sandbox reads.

What's the license?

MIT. Source, design notes, and the calibration workflow are on GitHub.

Stop eyeballing your clones.

Give an agent a number to climb.