design-check-mcp
v0.4.0
Published
Review a locally-running app against its Figma frames, entirely on-machine. MCP server + deterministic grading engine + local report viewer.
Readme
design-check-mcp
Review a locally-running app against its Figma frames, entirely on-machine. An MCP server wraps a deterministic grading engine and a local report viewer, so your coding agent can drive a design ↔ dev handoff review without any of your app's data, screenshots, or credentials ever leaving your machine.
Prerequisites
- Claude Code (or another agentic MCP host with vision — the agent needs to read screenshots to match screens to frames).
- The official Figma MCP server connected in that same host (design-check reads Figma frames through it; it never calls the Figma REST API or needs a Figma PAT).
- Chrome installed locally (capture drives a real, visible Chrome window via Playwright — not headless, so you can see and interact with it).
Install
Run this in your terminal — not in the agent chat. Pasting it into a chat message does nothing: the agent reads it as text instead of executing it.
claude mcp add design-check -- npx -y design-check-mcpThen start a new agent session. MCP servers load at session start, so a session that was already open will not see the tools. Verify the registration:
claude mcp list # should list: design-check … ✔ ConnectedPrefer to stay in the chat? Then ask your agent to do it, phrased as an instruction it will execute:
Install the design-check MCP server: run
claude mcp add design-check -- npx -y design-check-mcpin the shell, verify it appears inclaude mcp list, then remind me to start a new session so the tools load.
For a team repo, add --scope project after add — the server is then written
to a committable .mcp.json in the repo, and every session opened there gets it
automatically.
Registration gives your agent 15 tools (create_run, submit_figma,
declare_section, report_stale_frame, run_capture, capture_status,
get_match_task, submit_matches, grade, export_report, open_viewer,
get_run, list_runs, open_setup, get_setup) plus a design-check prompt
that walks the whole flow end to end.
Quickstart
Start your app locally, then just ask your agent:
design-check my app at localhost:3000 against <figma link>
The agent creates a run, pulls the Figma frames, captures your running screens, matches screens to frames, grades the diffs against tolerance rules, and opens the report viewer with the results.
v0.4 — trust & protocol
v0.4 hardens the agent-facing protocol so a run's config, frames, and findings can be trusted at each step:
- Form-first by default.
open_setup(the guided setup form) is the default front door for starting a run —create_runfails closed unless the agent passesuserConfirmedManualConfig: true, an attestation about the user's own words that they explicitly asked to skip the form and hand-author a config. Without it,create_runrefuses and points back toopen_setup. declare_section. When a Figma frame the agent wants to submit is a child of a Figma section (rather than listed directly in the config), the agent must calldeclare_sectionwith that section's child frame nodeIds (dot-frames excluded) before submitting any of them —submit_figmarefuses an undeclared child.- Frame PNGs are required.
submit_figmarequires a real frame PNG (framePngPath, magic-byte validated) for every frame — screens are graded against pixels, not just metadata. The only escape hatch ispngUnavailableReason: a reasoned explanation (shown verbatim in the report) for when the export genuinely fails after a retry. report_stale_frame. If a Figma node in the config no longer exists (deleted, moved, renamed), the agent callsreport_stale_frameinstead of silently skipping it. Stale pointers are reported as their own status in the findings, not dropped.gradepreconditions.graderefuses to run until every declared frame and section child has landed (viasubmit_figma,report_stale_frame, or an all-stale section) — one aggregated refusal listing everything still missing, with no "grade anyway" override.export_report/design-check-mcp export. Once a run is graded, export it as ONE self-contained HTML file — images inlined, no server, no chat context — for a designer to open by double-clicking. Call theexport_reporttool, or runnpx -y design-check-mcp export [runId] [--out <path>]from the terminal (runIddefaults to the most recent run). The file is written to.design-check/runs/<slug>/report.htmlby default — deliberately inside the gitignored.design-check/directory so a report full of real screenshots can never be committed by accident. It carries an internal-only banner: the screenshots may contain real customer data — never share it outside the team.
Guided setup
open_setup opens a short local web form for entering a run's details instead of
typing them into chat: project name, base URL, repeatable page-path/Figma-link
rows, an optional "Test login" (username + password, with a show/hide toggle on
the password field), and an optional "Extra context" field — freeform notes about
the app (e.g. "the promotions screens are the priority; the header should match
the Figma exactly"). Extra context guides screen-to-frame matching and the run's
closing summary; it never changes the automated findings themselves.
How login works
design-check never handles your credentials. Capture runs a visible, headed Chrome window; if it hits a login wall, it holds that window open and polls until you log in there yourself, then resumes automatically — the login and the capture share one browser session.
For testing accounts only, you can opt into scripted login by setting
DESIGN_CHECK_USERNAME / DESIGN_CHECK_PASSWORD in the server's own environment
(the env block of the entry the install command above added to your MCP config,
e.g. .mcp.json) — never paste credentials into chat, and never point this at a
real, privileged, or shared account.
The guided setup form's "Test login" fields are another way to provide the same credentials for a single run: enter a testing-only username and password there (held in memory for that session only, never written to disk) and capture picks them up automatically to drive scripted login — same mechanism as the env vars above, same rule: testing accounts only, never a real, privileged, or shared account. Leave both fields empty to log in yourself in the capture browser instead.
Where artifacts live
Everything a run produces — Figma captures, screenshots, DOM dumps, matches,
findings — is written under .design-check/runs/<run-slug>/ in the target repo.
Nothing is uploaded anywhere.
Viewer
The open_viewer tool starts the local report viewer for you and returns its URL.
You can also start it yourself at any time:
npx -y design-check-mcp view
# design-check viewer: http://localhost:4173Pass --port <n> to pick a specific port (it tries the next one up if that port is
taken).
Live progress: opening a run at /runs/<slug> shows per-screen status —
Capturing…, Login wall, Aligned, N must-fix · M should-fix, or Couldn't
align — that auto-refreshes until the whole run is graded, so you can watch a
long capture/grade cycle without re-polling by hand. Once graded (or any time
you want the full findings), the deck report lives at /runs/<slug>/report;
clicking a row in the progress view deep-links straight to that screen's slide.
Figma capture format
submit_figma ingests ONE raw JSON per frame, shaped:
{ "fileKey": "...", "nodeId": "5370:72571", "get_metadata": "<verbatim XML>", "get_design_context": "<verbatim code>", "get_variable_defs": { } }Hard rules (violations are rejected with pointers back to this section):
get_metadatamust be the verbatim output of the Figma MCPget_metadatatool — an XML tree where every node hasid,x,y,width,height. Do not hand-summarize or re-shape it.get_design_contextmust be the verbatim output ofget_design_context— code containingdata-node-id="..."attributes. It is NOT the metadata XML; the two are different tools and cannot substitute for each other.nodeIduses the colon form (5370:72571), not the URL dash form (5370-72571).
Large frames (chunked refetch): on big frames get_design_context downgrades to sparse metadata. Re-fetch per top-level child: call get_design_context once per child nodeId (children are listed in get_metadata), then save get_design_context as an ARRAY of those code strings. Only one metadata node id needs to appear across the joined array — chunks that only anchor their own subtree are fine. Never drop a chunk silently.
What design-check can grade
design-check compares URL-addressable states: one capture per route, graded against one Figma frame. Anything behind clicks — wizard steps, modals, validation states — is not captured. Give interactive states their own routes if you want them graded, or pair the page with the Figma frame for its initial state. Frames for other states of the same page are reported as "in Figma section, not reviewable (interactive states)".
Viewer server lifecycle
open_viewer / open_setup start one local HTTP server inside the MCP server process (default port 4173, 127.0.0.1 only). It keeps running for the whole MCP session — subsequent runs reuse it. It stops when the MCP session ends (e.g. Claude Code exits). The standalone npx design-check-mcp view command runs the same viewer as its own process; stop it with Ctrl-C.
