Skip to main content
Glama

Driftcheck is a local MCP server. It captures the screen of an Android device or the iOS Simulator, compares it 1:1 with the Figma frame and gives your agent what it needs to close the gap:

  • What's different: position, size, text, font, colors, corner radius, borders, and missing icons or images, including layers with no text, found by how they look.

  • How to fix it, written with your framework's properties: Margin, android:layout_marginTop, Modifier.padding, .frame, EdgeInsets…

  • A fidelity score that tracks progress from one fix to the next, and an HTML report to share with your team.

It works with real-world design kits, where layers are called Frame or Text, and without a Figma token through the Driftcheck Figma plugin.

Quick start

You need the .NET 10 SDK, plus adb for Android or Xcode for the iOS Simulator. Install Driftcheck in one click:

VS Code asks for your Figma token; leave it empty to use the Figma plugin instead. In Cursor, add the token to FIGMA_TOKEN in the server's configuration afterwards.

Or add it to any MCP client by hand; dotnet dnx downloads it from NuGet the first time:

{
  "mcpServers": {
    "driftcheck": {
      "command": "dotnet",
      "args": ["dnx", "Driftcheck", "--yes"],
      "env": { "FIGMA_TOKEN": "<your Figma token>" }
    }
  }
}

With Claude Code:

claude mcp add driftcheck -e FIGMA_TOKEN=<your Figma token> -- dotnet dnx Driftcheck --yes

Then run your app and ask your agent:

Compare the app on the emulator with https://www.figma.com/design/…?node-id=… and fix the differences.

Related MCP server: ui-design-to-code-mcp

What your agent gets back

compare_screen answers with a report the agent can act on (excerpt from the demo):

**Fidelity: 75 %** — 15 of 20 elements as in Figma

| Element                    | Property   | Figma   | App     |
|----------------------------|------------|---------|---------|
| Nice to meet you           | height     | 31.5    | 25.5    |
| Text "Sign up with Google" | x          | 131.9   | 125     |
| Shape "Sign up"            | background | #DBC6F8 | #BAD1BB |

## How to fix it

- **Nice to meet you**
  - The glyphs don't measure the same as in Figma; check the font: in Figma it is Inter 32 weight 600.
    `FontFamily="Inter"`, `FontSize="32"`, `CharacterSpacing`
- **Shape "Sign up"**
  - `BackgroundColor="#9E56FF"` (it renders as #DBC6F8 in the design: the layer or a container has opacity)

## Design non-text layers not found in the app (1)

- Icon (x=108.2 y=392.3 w=20 h=20) — Missing a 20×20 dp icon (the Figma layer is 24×24)

Along with it come report.html (the interactive report above: slider, opacity, diff and side by side), side_by_side.png and diff.png.

Supported platforms

Device

How it reads the screen

Android emulator or device

adb: screencap + uiautomator dump (the AutomationId is the resource-id)

iOS Simulator (macOS)

simctl + an XCUITest helper that reads the accessibility tree (the AutomationId is the accessibilityIdentifier)

Fix suggestions are written for .NET MAUI, Android views, Jetpack Compose, UIKit, SwiftUI and Flutter, or in plain terms if you don't pass a framework. The comparison itself doesn't depend on the framework: it works from what's on the screen.

MCP tools

Tool

Use

list_devices

Connected Android devices and booted iOS simulators

get_design_specs

Sizes, colors, texts and fonts of the frame (before writing the UI code)

capture_screen

Capture + app elements with their AutomationId

compare_screen

Full design vs. app comparison: fidelity, differences, how to fix them and report.html

import_design

Imports a .driftcheck.json from the Figma plugin that ended up outside Downloads

How it works

Driftcheck downloads the frame once and keeps it in a local cache, recomputes Figma's auto layout, and matches layers to app elements by AutomationId, by visible text or by position. Texts are compared by their visible glyphs rather than their boxes, non-text layers are found in the screenshot by how they look, and a device slightly wider or narrower than the frame is handled by anchoring each element where it fits best. The full walkthrough is in How Driftcheck compares.

Figma API limits

Figma limits how many calls a token can make, and on some seat types the quota is very low: once it runs out, it responds 429 with waits of days. That's why:

  • The JSON and PNG of each frame are stored in a local cache and reused on every comparison. Pass refresh=true to get_design_specs or compare_screen to download a frame that changed in Figma.

  • If Figma responds 429, the error says how long to wait.

  • Without a token or quota, the Figma plugin in tools/figma-export-plugin exports the frame (JSON + 2x render) to a .driftcheck.json file without using the REST API. If it lands in Downloads, Driftcheck picks it up on its own from the frame link; if it lands in another folder, import it with import_design. See the plugin README.

Requirements

  • .NET 10 SDK

  • For Android: Android SDK with adb (detected from ANDROID_HOME, ANDROID_SDK_ROOT or the default paths on Windows and macOS)

  • For iOS: macOS with Xcode and a booted simulator. No idb or WebDriverAgent needed

  • A FIGMA_TOKEN environment variable with a Figma Personal Access Token (scope File content: Read-only), or the Figma plugin to work without the API

  • Optional: DRIFTCHECK_OUTPUT to change the output folder (default: driftcheck in the temp folder)

  • Optional: DRIFTCHECK_EXPORTS to change where plugin exports are looked up (default: Downloads)

Current limitations

  • iOS on the simulator only (not on physical devices).

  • Visible area only (no scrolling).

  • Doesn't measure font size in the app (uiautomator and XCUITest don't expose it); get_design_specs provides it as a reference.

Running from source

git clone https://github.com/wilsonvargas/Driftcheck.git
cd Driftcheck
dotnet build src/Driftcheck.Mcp

The .mcp.json at the root registers the server for MCP clients that read it when opening this folder. To use it from another project, add the server to your client's MCP configuration:

{
  "mcpServers": {
    "driftcheck": {
      "command": "dotnet",
      "args": ["<repo path>/src/Driftcheck.Mcp/bin/Debug/net10.0/Driftcheck.Mcp.dll"]
    }
  }
}

Repository layout

  • src/Driftcheck.Mcp — MCP server

  • tools/ios-accessibility-helper — UI test that dumps the iOS Simulator accessibility tree (copied next to the server on build)

  • tools/figma-export-plugin — Figma plugin that exports a frame without the REST API

  • samples/PayloMaui — MAUI test app with screens from the Paylo kit by Marvilo (CC BY 4.0) For the iOS Simulator: dotnet build -f net10.0-ios -p:TargetFrameworks=net10.0-ios (limiting TargetFrameworks avoids requiring the Mac Catalyst workload), then xcrun simctl install booted bin/Debug/net10.0-ios/iossimulator-arm64/PayloMaui.app and xcrun simctl launch booted com.companyname.paylomaui. The Xcode version must be the one the iOS workload asks for; if several are installed, pick it with DEVELOPER_DIR.

  • samples/PayloNative — the same sign-up screen with native Android views (Java, no MAUI or AndroidX), to check that the comparison doesn't depend on MAUI. ./build.ps1 builds it with the SDK tools (no Gradle) and installs it on the emulator

  • samples/PayloIos — the same screen with native iOS views (UIKit in Swift, no MAUI or storyboard). ./build.sh builds it with swiftc (no Xcode project) and installs it on the booted simulator

  • tests/Driftcheck.Tests — comparer tests with synthetic screens

  • docs — the detailed comparison walkthrough and the demo report published on GitHub Pages

To compare the sample apps, open the Paylo kit in the Figma Community, duplicate it to your drafts, select the frame Component 113 (the sign-up screen) and use Copy link to selection. Then run a sample on the emulator or simulator and ask your agent to compare it with that link.

Contributing

Issues and pull requests are welcome. See CONTRIBUTING.md for how to build, test and propose changes, and SECURITY.md to report a vulnerability privately.

License

Driftcheck is released under the MIT License. Third-party material in the sample apps keeps its own license: see THIRD_PARTY_NOTICES.md.

Available Tools

5 tools
capture_screenA

Takes a screenshot of the device (Android or iOS Simulator) and lists the visible elements with their AutomationId (resource-id on Android, accessibilityIdentifier on iOS), text and bounds in dp or pt. On iOS it takes a few more seconds: it reads the accessibility tree with a UI test, which is built the first time.

NameRequiredDescriptionDefault
deviceSerialNoDevice serial or UDID (from list_devices). Optional: uses the first one.

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does add real operational context: iOS is slower because it reads the accessibility tree via a UI test that is compiled the first time. It also discloses unit differences (dp vs pt) and platform-specific id attributes. It stops short of saying whether the screenshot is persisted to a file path or returned inline.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with the core action and output shape, then the platform caveat. Every clause carries information; nothing is redundant, though the iOS sentence is dense.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, so the description must explain returns — it does so by enumerating element fields and units, which is the key information. The remaining gap is the screenshot artifact itself: whether a path or image reference is returned, and any device-availability errors.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the single optional deviceSerial parameter is fully documented there, including the default of 'first one'. The description adds no parameter guidance, which is acceptable at full coverage but earns only the baseline.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Takes a screenshot of the device') and goes further to specify the returned payload: visible elements with AutomationId, text, and bounds. Platform-specific naming (resource-id on Android, accessibilityIdentifier on iOS) makes it unmistakable against siblings like compare_screen or get_design_specs.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied by 'Takes a screenshot... and lists the visible elements', but there is no explicit when-to-use vs compare_screen or get_design_specs, which are the closest alternatives for screen inspection. The iOS cost warning hints at a tradeoff but never names when to pick an alternative.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

compare_screenA

Compares the app's current screen on the device (Android or iOS Simulator) with a Figma frame. Matches Figma layers to app elements by visible text, name = AutomationId or position, and reports differences in position, size, text and background color. Non-text layers (buttons, fields, lines, icons, images) are found in the screenshot by how they look, comparing box, fill, corner radius, border and content. Reports fidelity (% of elements as in Figma, and how much it changed since the previous comparison) and how to fix each difference with the framework's properties. Generates report.html (interactive report to show the user), side_by_side.png and diff.png (in diff.png: green = matches, orange = differs, dotted blue = where Figma says it should be). Open the images for a visual review. If the screenshot doesn't look like the design (a loading screen), it waits and captures again up to 3 times.

NameRequiredDescriptionDefault
refreshNotrue to download the Figma frame again instead of using the local cache. Use it only if the design changed: each download uses Figma API quota.
figmaUrlYesFigma frame URL (Copy link to selection), must include ?node-id=
frameworkNoApp framework, to write the suggestions with its properties: maui, android (XML views), compose, uikit, swiftui or flutter. Optional.
toleranceNoPosition/size tolerance in dp or pt. Default 2.
projectPathNoApp project folder. Optional: if it has files newer than the installed app, the report warns that the app needs to be rebuilt and reinstalled.
deviceSerialNoDevice serial or UDID. Optional: uses the first one.
colorToleranceNoColor tolerance (RGB distance 0–441). Default 24.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so extensively: it explains matching heuristics, diff color meanings, generated files, cache/API quota implications, fidelity reporting, and automatic retry up to 3 times. This gives an agent detailed behavioral context beyond the schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is dense but front-loaded with the core comparison purpose and then layers on behavior and outputs. Every sentence carries useful information, though the single long paragraph could be more structurally scannable for such a complex tool.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex visual comparison tool with no output schema and no annotations, the description is remarkably complete: it covers inputs, matching logic, output artifacts, retry behavior, and reporting. An agent has enough information to invoke it correctly and interpret results.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds meaningful semantics: refresh uses Figma API quota, projectPath triggers rebuild warnings, deviceSerial defaults to first device, and tolerance/colorTolerance define comparison thresholds. It could still clarify figmaUrl format, but overall it adds value beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: compares the app's current screen with a Figma frame. It details matching strategy, reported differences, generated artifacts, and retry behavior, making it clearly distinct from siblings like capture_screen and get_design_specs.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Clearly establishes the comparison context and when retry happens (loading screen), but does not explicitly say when to use this versus siblings such as get_design_specs or capture_screen. The usage context is strong but lacks explicit alternatives or exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_design_specsA

Returns the measurements of the Figma frame: position, size, colors, texts and typography of every named layer. Use it BEFORE writing a screen's UI code. If deviceWidth is passed, values are scaled to that width in dp or pt.

NameRequiredDescriptionDefault
refreshNotrue to download the Figma frame again instead of using the local cache. Use it only if the design changed: each download uses Figma API quota.
figmaUrlYesFigma frame URL (Copy link to selection), must include ?node-id=
deviceWidthNoLogical width of the target device in dp or pt (e.g. 412 for Pixel 7, 402 for iPhone 17). Optional.

TDQS

A4.2/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the burden. It usefully discloses the scaling behavior when deviceWidth is passed, but omits caching/refresh semantics and API-quota cost (only covered in the schema) and never explicitly states this is a read-only, side-effect-free operation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three tight sentences, front-loaded with what is returned, then when to use it, then the one optional behavior. Nothing is wasted.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, and the description compensates by enumerating the returned measurements, so an agent knows what to expect. Minor gap: refresh/cache behavior is only explained in the schema, not the description.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3; the description adds value by explaining that passing deviceWidth scales returned values to that width in dp or pt, which is behavioral meaning the schema does not state.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ('Returns the measurements') and enumerates exactly what comes back: position, size, colors, texts, typography of every named layer. That is far more concrete than the sibling names (compare_screen, capture_screen) and lets an agent tell the tools apart.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit timing guidance: 'Use it BEFORE writing a screen's UI code.' Clear context for when to call it. It does not name an alternative tool or state when not to use it, so it falls short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

import_designA

Imports a .driftcheck.json file exported with the Driftcheck plugin in Figma, to compare without the Figma API (no token or no quota). Exports in the Downloads folder are picked up on their own; this is for the ones that ended up in another folder. Then compare with the frame link, as usual.

NameRequiredDescriptionDefault
pathYesPath of the .driftcheck.json file

TDQS

A4.2/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description does useful work by disclosing that no Figma token or quota is required and that Downloads-folder exports are handled automatically. However, it does not explain what importing actually changes internally, how validation failures behave, or what the tool returns.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core action and purpose, then adds the exception for non-Downloads files and the follow-up comparison step. There is no filler, and every sentence contributes useful information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a low-complexity, one-parameter import tool with no annotations or output schema, the description covers purpose, usage conditions, and the no-API constraint well. It stops short of describing import results or error behavior, but the essential invocation context is present.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, and the single path parameter is already described in the schema as the path of the .driftcheck.json file. The description adds useful context about the file's plugin origin and non-Downloads use case, but it does not add path syntax or format guidance.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: importing a .driftcheck.json file exported from the Figma Driftcheck plugin. It also explains the purpose and condition, comparing without the Figma API when there is no token or quota, which distinguishes it from API-dependent siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit when-to-use guidance: files in Downloads are picked up automatically, so this tool is for files that ended up in another folder. It also indicates the workflow continues by comparing with the frame link as usual.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_devicesA

Lists the Android devices and emulators connected through adb and, on macOS, the booted iOS simulators.

NameRequiredDescriptionDefault

No parameters

TDQS

A3.7/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description must carry the burden itself; it does disclose that discovery is adb-based and that iOS simulator support is macOS-only, which is valuable context. It does not state read-only nature explicitly or describe the shape of the returned list (device IDs, states, offline devices).

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single front-loaded sentence with no filler; the platform qualifier is placed as a trailing caveat rather than obscuring the main purpose.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter listing tool with no output schema, the description covers what is enumerated and the platform limitation. Slightly more on the returned entries (e.g., whether offline/unauthorized devices are included) would make it fully complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so the baseline of 4 applies; there is nothing for the description to disambiguate beyond confirming the call is argument-free.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

Names a specific verb (lists) and resource (Android devices/emulators via adb, plus booted iOS simulators on macOS), including the platform caveat. It is unambiguous, though it does not need to differentiate from the unrelated screen-capture siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No explicit when-to-use or when-not-to-use guidance; the discovery use case (before capture_screen/compare_screen) is only implied by context. The absence of prerequisites or alternatives keeps it at minimum-viable.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 5 tool updatesv0.1.0
    • First observedcapture_screen
    • First observedcompare_screen
    • First observedget_design_specs
    • First observedimport_design
    • First observedlist_devices

TDQS

A4.1/5.0

Scored across 5 tools

Disambiguation4/5

Each tool has a clear primary purpose, but capture_screen and compare_screen could be mistaken at a glance since compare_screen internally captures and analyzes the screen. get_design_specs and import_design also both deal with Figma source data, though their descriptions clearly distinguish API vs offline export use.

Naming Consistency5/5

All tools follow a consistent snake_case verb_noun pattern: list_devices, compare_screen, capture_screen, get_design_specs, import_design. No mixed conventions or vague standalone verbs.

Tool Count5/5

Five tools is well-scoped for a visual design comparison server. Each tool covers a distinct step in the workflow: device discovery, screenshot capture, Figma spec retrieval, offline design import, and comparison.

Completeness4/5

The core workflow is covered: discover devices, get design specs or import design data, capture the current UI, and compare it against Figma with reports. Minor gaps exist around explicit device selection or app navigation/state control, but agents can likely work around these.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Local MCP server for Android and iOS mobile automation and performance telemetry, enabling device control (screenshot, tap, swipe, input, app launch) and metric collection (CPU, memory, launch time) via ADB, simctl, and WebDriverAgent with SQLite session history.
    3 npm
    1
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    A Model Context Protocol (MCP) server for iOS Simulator automation. Enables AI assistants to visually interact with iOS apps running in the simulator.
    97 npm
    1
    MIT