Skip to main content
Glama

ui-diff-mcp

MCP server for comparing mobile app screenshots against mockup designs using free-first visual model diffing. Calorix is the primary integration target.

Design spec: docs/superpowers/specs/2026-06-12-ui-diff-mcp-research-design.md

Free-First Default

The default mode (free) never calls paid OpenRouter routes. It probes direct Gemini routes first, then direct Mistral routes, OpenCode Zen's image-capable mimo-v2.5-free, native NVIDIA free VLM endpoints, and finally OpenRouter :free routes. Every route must pass the role's real image-count and JSON probe before selection. Paid OpenRouter models are disabled unless mode: "paid" is passed and UI_DIFF_ENABLE_PAID_MODE=1 is set.

OpenCode currently also lists deepseek-v4-flash-free, but its model metadata is text-only. It is intentionally excluded from auditor, reviewer, and target-recovery roles because those roles require four or five crop images. OpenCode documents its free models as limited-time routes, so catalog presence never replaces runtime probes.

Gemini model-list probing on this machine showed gemini-3.1-pro-preview, gemini-3.5-flash, and gemini-3.1-flash-lite are visible. A direct live call to gemini-3.1-pro-preview returned free-tier quota limit 0, so it remains first in the quality ranking but is expected to fail closed unless quota is available. gemini-3.5-flash is the current direct Gemini live-gate model.

Mistral model-list probing showed vision-capable routes including mistral-large-2512, mistral-medium-2604, mistral-small-2603, ministral-14b-2512, and ministral-8b-2512. Live role probing on this machine showed ministral-14b-2512 and ministral-8b-2512 correctly handle the five-image payload required by auditor/reviewer/recovery. mistral-large-2512, mistral-medium-2604, and mistral-small-2603 miscounted or misclassified simple probe images, so they are not current pipeline routes.

Before starting a free-model run, the pipeline estimates the required request count and checks available quota against the OpenRouter key info endpoint. If estimated calls exceed available free quota, the run exits immediately with status: "insufficient_free_quota" rather than consuming quota silently.

Related MCP server: imugi

Modes

mode selects the provider/model route policy. It does not select how much of the UI to inspect.

Mode

Behavior

free

Default. Gemini direct, Mistral direct, OpenCode MiMo, NVIDIA free endpoints, then OpenRouter :free. Never paid OpenRouter.

free_gemini

Only direct Gemini routes. Current live gate uses gemini-3.5-flash.

free_mistral

Only direct Mistral routes. Current live gate uses ministral-14b-2512.

free_opencode

Only OpenCode Zen visual routes. Currently mimo-v2.5-free.

free_openrouter

Only OpenRouter :free routes.

free_nvidia

Only native NVIDIA free endpoint routes.

paid

Explicit opt-in requiring UI_DIFF_ENABLE_PAID_MODE=1. Records paid model use in report.json.

deterministic_only

No VLM calls. Returns deterministic signal evidence only.

Diff Scopes

Use diffScope to select the visual scope independently from provider mode.

{ "kind": "screen" }

Audits the whole screen first: global placement, major color/appearance changes, broad shape/border/layer differences, and whole-screen diff masks. Target-level recovery is bypassed.

{ "kind": "regions", "regions": ["top", "nav"] }

Audits only selected deterministic regions. Current region names are top, middle, bottom, header, content, and nav. Target recovery is restricted to uncovered components inside the selected regions.

{ "kind": "target", "query": "scan button" }

Resolves a target by locator label, visible text, and element type, then audits only the best matching pair. If the target cannot be resolved, the report includes a warning and does not pretend the target was checked.

{ "kind": "full" }

Default. Runs screen/region summaries, scope-level VLM audit where deterministic triggers fire, and the existing target-level audit/recovery path.

Example MCP payload:

{
  "expectedImagePath": "C:/mockups/Today.png",
  "actualImagePath": "C:/screenshots/today.png",
  "mode": "free",
  "diffScope": { "kind": "regions", "regions": ["nav"] }
}

Artifacts As Machine Evidence

All generated images (pixel diff, directional overlay, crop pairs, recovery crops) are machine evidence consumed by audit and recovery models. They are not a manual inspection workflow. Do not rely on visual artifact review as a substitute for structured report.json output.

final-diff-groups-overlay.png shows visual clusters of final diffs. It is not a semantic parent/child hierarchy: a whole-screen cluster and localized clusters can be siblings when they explain different criteria. Use semantic-hierarchy-overlay.png and semantic-hierarchy-legend.json to inspect the detected UI structure such as screen, nav, macro card, macro circle, and repeated cards.

Report Parts And Usage Accounting

report.json is the slim manifest. Large report sections are written as referenced JSON parts under artifacts/parts/ instead of being duplicated inline:

  • elements.json

  • pairs.json

  • diffs.json

  • unresolved-regions.json

  • debug-summary.json

  • usage-summary.json

  • scope-summary.json

reportParts[].path is relative to the report.json directory. read_ui_diff_report hydrates these parts before returning the report, so existing MCP consumers still receive a full schema-valid report even though the on-disk manifest keeps elements, pairs, diffs, and unresolved regions compact.

usageSummary is first-class run-level accounting. It records input tokens, output tokens, total tokens, reasoning tokens, successful calls, failed calls, fallbacks, route exhaustion, and duration totals by phase, role, and provider/model route. If a provider reports only total tokens, input/output are left as zero and totalOnlyUsageCalls increments; the MCP does not invent a fake split.

LocateAnything Live Gate Sizing

LOCATEANYTHING_MAX_DIMENSION controls the largest image dimension sent to the LocateAnything sidecar. The default remains 1200 for detail.

600 is a local timeout workaround, not a quality default. It shrinks a 1206x2622 Calorix mockup to roughly 276x600 for the locator, which can hide small icons, thin borders, and text. Prefer the highest dimension that fits the sidecar budget, and run the sequential locator benchmark before production sign-off:

export UI_DIFF_LIVE_EXPECTED_IMAGE="/home/agent-runner/projects/calorix/docs/design-handoff/placeholder-app/reference-images/today--dark.png"
export LOCATEANYTHING_SIDECAR_URL="http://127.0.0.1:39731"
export UI_DIFF_LOCATOR_BENCHMARK_DIMENSIONS="600,900,1200"
npm run benchmark:locator

For Calorix release evidence, UI_DIFF_LIVE_ACTUAL_IMAGE normally stays unset so the gate can prove fresh acquisition. The MCP capture implementation and Calorix live helper now accept adbExecutable/adbSerial options (or UI_DIFF_ADB_EXECUTABLE/UI_DIFF_ADB_SERIAL environment fallbacks) and route every ADB invocation through the resolved executable with optional -s serial prefix. On this Pi, set UI_DIFF_ADB_EXECUTABLE=/home/agent-runner/.local/bin/phone-adb and leave UI_DIFF_ADB_SERIAL unset — the wrapper already pins the serial. For generic ADB executables that do not embed a serial, set UI_DIFF_ADB_SERIAL to the target device serial. Manual /home/agent-runner/.local/bin/phone-adb exec-out screencap -p remains valid for diagnostic comparisons. An explicit UI_DIFF_LIVE_ACTUAL_IMAGE is logged as an override and is not fresh release proof.

Raspberry Pi Physical-Phone Evidence Policy

The dedicated Samsung SM-G780G, Android 13, serial R58R61161NA is the only default local Android target. Every device command must use /home/agent-runner/.local/bin/phone-adb, which pins the serial. Never use plain adb or implicit device selection. Cuttlefish, android-vm, ReDroid, desktop AVDs, and local emulators are retired; GitHub's x86_64 emulator remains an independent CI gate.

Identity preflight:

/home/agent-runner/.local/bin/phone-adb devices -l
/home/agent-runner/.local/bin/phone-adb shell getprop ro.product.model
/home/agent-runner/.local/bin/phone-adb shell getprop ro.build.version.release
/home/agent-runner/.local/bin/phone-adb get-serialno

Verified Calorix Actions APKs

Calorix's GitHub Actions workflow .github/workflows/android-build.yml is named Build Android APK. A successful run publishes an artifact named android-apk-<commit-sha> containing the versioned release APK (calorix-<version>-android-release.apk) and its matching .sha256 file.

Use the fetcher only for a committed source revision whose cleanliness has already been established by the caller:

bash scripts/fetch-calorix-actions-apk.sh \
  --repo ia23a-lachnita/calorix \
  --source-sha 1f538641f5e5f5c4a48c95cdfb97462838187106 \
  --source-clean \
  --output "$HOME/Downloads/calorix-release.apk"

--source-clean is an affirmative trust-boundary attestation. The script intentionally does not inspect any local Calorix checkout, so an omitted clean proof or --source-dirty rejects with uncommitted_source. This prevents the known dirty local .mcp.json from being mistaken for clean, current APK evidence. The script queries only the exact successful Build Android APK run for the requested SHA, downloads the named artifact from its immutable numeric run ID, accepts exactly the APK plus same-basename checksum, verifies the SHA-256, then atomically writes the APK and an adjacent .verified.json provenance record. It never overwrites an existing output or verification record.

Calorix live gates validate the canonical default expected reference before any device or pipeline work: /home/agent-runner/projects/calorix/docs/design-handoff/placeholder-app/reference-images/today--dark.png, its adjacent reference-images-manifest.json, and SHA-256 73BA85F25489C8D45BEAB57DD1B317138870CE8360FE0F4399AB0737A5E505F1. An explicit UI_DIFF_LIVE_EXPECTED_IMAGE remains supported, but must be readable; reference-images-buggy and good-screenshots are never fallback sources.

Calorix reports persist a report-safe inputProvenance record whose expected and actual SHA-256 identities are computed from the exact image bytes by the pipeline. A manifest entry is included only after the pipeline verifies that its recorded hash matches the expected image bytes. Acquisition sources such as auto_capture and env_override are stored separately with verification:"caller_attested"; they are not presented as independently verified facts. Public MCP requests cannot supply computed hashes. On resume, omitted provenance inherits the stored effective record; an explicit replacement is accepted only when the recomputed expected and actual image identities match the resumed report.

Every report records locatorInputSizing, including original image size, sent image size, scale, maxDimension, and whether actual elements were independently located or projected from expected elements. Runs also save the exact image payloads sent to the sidecar as locator-input-expected.png and, in dual-locator mode, locator-input-actual.png; these appear in runArtifacts as locator_input_expected and locator_input_actual.

Locator debugging uses a three-step artifact chain:

  • locator-input-expected.png: the exact expected-image bytes sent to LocateAnything.

  • locator-expected-overlay.png: the located target boxes drawn on the normalized expected image.

  • locator-actual-projected-overlay.png: in default single-pass projection mode, the expected target boxes projected onto the normalized actual screenshot. This does not mean the actual screenshot was sent to LocateAnything; it shows where the expected targets land on the actual image before the diff/audit stages inspect those projected regions.

The overlay files are indexed in runArtifacts as locator_expected_overlay, locator_actual_overlay, and locator_overlay_legend. The legend maps compact overlay labels such as E001 and P001 back to exact element IDs, types, query IDs, source, and boxes.

Installation

npm install

Verification

npm run verify

Build

npm run build

Running the server

node dist/src/index.js

Environment Variables

Copy .env.example and fill in the relevant keys.

Variable

Required

Default

Description

OPENCODE_API_KEY

No

public

Optional OpenCode Zen credential override. The current free route accepts the public credential.

OPENCODE_ZEN_BASE_URL

No

https://opencode.ai/zen/v1

OpenCode Zen API base URL.

GEMINI_API_KEY

For Gemini direct mode

Gemini API key from AI Studio. Routes are always probe-gated because visible models may have zero free-tier quota.

GEMINI_BASE_URL

No

https://generativelanguage.googleapis.com/v1beta

Override Gemini API base URL.

MISTRAL_API_KEY

For Mistral direct mode

Mistral API key for direct vision routes.

MISTRAL_BASE_URL

No

https://api.mistral.ai/v1

Override Mistral API base URL.

OPENROUTER_API_KEY

For OpenRouter free mode

OpenRouter API key. Free-tier account sufficient for :free routes.

NVIDIA_API_KEY

For NVIDIA free mode

NVIDIA Build/NIM API key for native NVIDIA free VLM endpoints.

NVIDIA_VLM_BASE_URL

No

https://integrate.api.nvidia.com/v1

Override NVIDIA base URL for self-hosted NIM.

UI_DIFF_ENABLE_PAID_MODE

For paid mode only

Must be exactly 1 before mode: "paid" can use paid routes.

LOCATEANYTHING_SIDECAR_URL

No

http://127.0.0.1:39731

URL of the LocateAnything sidecar. On the Pi this is the broker for the remote GPU host, never a locally started process.

LOCATEANYTHING_BROKER_STARTUP_TIMEOUT_MS

No

600000

Broker cold-start budget. Must be a finite positive integer, max 600000.

LOCATEANYTHING_EAGLE_EMBODIED_DIR

For the local launcher only (archival/local-development; not permitted on the Pi)

Path to Eagle Embodied install.

LOCATEANYTHING_PYTHON

For the local launcher only (archival/local-development; not permitted on the Pi)

Known local venv, then python

Python interpreter for local sidecar startup.

LOCATEANYTHING_SKIP_MODEL

No

Diagnostic mode only. 1 skips the LocateAnything 3B worker and uses CV/OCR/optional parser lanes; do not use as full locator-model sign-off.

LOCATEANYTHING_IN_TOKEN_LIMIT

No

4096

Image token budget for local sidecar.

LOCATEANYTHING_GENERATION_MODE

No

hybrid

Sidecar worker mode: fast, slow, or hybrid.

LOCATEANYTHING_MAX_NEW_TOKENS

No

512

Sidecar generation cap.

UI_DIFF_MAX_AUDIT_PAIRS

No

Cap the number of element pairs audited. Bounded runs are marked auditLimited: true in the report. Bounded smoke and full classification are distinguishable via visualClassificationStatus and auditScope.

UI_DIFF_ADB_EXECUTABLE

No

adb

Override the ADB executable for mobile capture. Used by captureMobileScreen and Calorix device helpers. On the Pi, set to /home/agent-runner/.local/bin/phone-adb which already pins the serial; leave UI_DIFF_ADB_SERIAL unset.

UI_DIFF_ADB_SERIAL

No

ADB serial for device targeting. Prepends -s <serial> to every ADB invocation. Only needed when UI_DIFF_ADB_EXECUTABLE points to a generic adb that does not embed a serial.

This implementation requires no user-authored target map, ROI map, ignore mask, or anchor dump.

Configuring OpenCode Zen Free Models

No local OpenCode daemon or opencode run process is required. The MCP calls the OpenAI-compatible Zen API directly. The current free route works with the default public credential; set OPENCODE_API_KEY only when OpenCode provides a dedicated key.

$env:RUN_OPENCODE_LIVE="1"
npm run verify:opencode-live

The gate verifies the live catalog, a one-image structured response, and one deduplicated five-image probe shared across auditor, reviewer, and target recovery.

Configuring Direct Gemini Models

Set GEMINI_API_KEY to an AI Studio key. The pipeline probes direct Gemini routes before using them; gemini-3.1-pro-preview may be visible but quota-blocked on the free tier, so gemini-3.5-flash is the current live-gate route.

$env:GEMINI_API_KEY="..."
$env:RUN_GEMINI_LIVE="1"
npm run verify:gemini-live

Configuring Direct Mistral Models

Set MISTRAL_API_KEY to a Mistral API key. The pipeline starts with ministral-14b-2512 because it passed the same five-image role probe used by the auditor/reviewer/recovery gates; ministral-8b-2512 is the next Mistral fallback.

$env:MISTRAL_API_KEY="..."
$env:RUN_MISTRAL_LIVE="1"
npm run verify:mistral-live

Configuring Native NVIDIA Free Models

Set NVIDIA_API_KEY to a NVIDIA Build API key. The pipeline probes native NVIDIA candidates from CANONICAL_MODEL_RANKING and selects the highest-quality passing model. Use NVIDIA_VLM_BASE_URL to point at a self-hosted NIM instead.

$env:NVIDIA_API_KEY="nvapi-..."
# $env:NVIDIA_VLM_BASE_URL="http://localhost:8000/v1"  # for self-hosted NIM

Run the NVIDIA live gate to verify:

$env:RUN_NVIDIA_LIVE="1"
npm run verify:nvidia-live

Configuring OpenRouter Free Models

Set OPENROUTER_API_KEY to an OpenRouter key (free-tier account works). The pipeline:

  1. Estimates required calls (probes + audit + recovery + review).

  2. Queries GET https://openrouter.ai/api/v1/key for limit_remaining.

  3. Exits with insufficient_free_quota if estimated calls exceed available quota.

  4. Throttles OpenRouter free calls to ≤ 18 requests/minute.

$env:OPENROUTER_API_KEY="sk-or-..."

Run the free live gate to verify:

$env:RUN_FREE_LIVE="1"
npm run verify:free-live

LocateAnything Sidecar (Windows)

This section is explicitly operator-managed historical/reference setup for the Windows GPU host itself. Agents must never act on it — no SSH into the Windows host, no remote power management, and no running these local-startup commands from the Pi or any other agent-controlled machine. On the Pi, every locator call goes through the broker (LOCATEANYTHING_SIDECAR_URL) described below; the commands below are kept only as provenance for how the broker's host machine is set up by its human operator.

The MCP calls a sidecar endpoint at POST /v1/locate-ui-elements. Start the local wrapper after installing NVIDIA's Eagle Embodied package:

git clone https://github.com/NVlabs/Eagle.git C:\Users\xursc\projects\Eagle
python -m venv C:\Users\xursc\projects\.venvs\ui-diff-mcp-locateanything
C:\Users\xursc\projects\.venvs\ui-diff-mcp-locateanything\Scripts\python.exe -m pip install --upgrade pip setuptools wheel
C:\Users\xursc\projects\.venvs\ui-diff-mcp-locateanything\Scripts\python.exe -m pip install torch torchvision --index-url https://download.pytorch.org/whl/cu128
cd C:\Users\xursc\projects\Eagle\Embodied
C:\Users\xursc\projects\.venvs\ui-diff-mcp-locateanything\Scripts\python.exe -m pip install --no-deps -e .
cd C:\Users\xursc\projects\ui-diff-mcp
C:\Users\xursc\projects\.venvs\ui-diff-mcp-locateanything\Scripts\python.exe -m pip install -r sidecars\locateanything\requirements.txt
$env:LOCATEANYTHING_EAGLE_EMBODIED_DIR="C:\Users\xursc\projects\Eagle\Embodied"
$env:LOCATEANYTHING_PYTHON="C:\Users\xursc\projects\.venvs\ui-diff-mcp-locateanything\Scripts\python.exe"
.\scripts\start-locateanything-sidecar.ps1

The startup script prefers LOCATEANYTHING_PYTHON, then the known local venv at C:\Users\xursc\projects\.venvs\ui-diff-mcp-locateanything\Scripts\python.exe, then plain python, and prints the interpreter it selected. This is the machine that hosts the model behind the broker; consumers (including the Pi's Calorix live-test helper) never resolve a Python interpreter themselves — they only wait on the broker's /health contract, and fail fast if it reports an error instead of waiting for the full readiness timeout.

LocateAnything (Linux / Raspberry Pi)

The Pi never starts LocateAnything locally. Every locator call goes through the broker at LOCATEANYTHING_SIDECAR_URL (default http://127.0.0.1:39731), served by the remote Windows RTX 3070 host described above. ensureSidecarRunning() in tests/helpers/sidecar-manager.ts is the broker-only live harness manager: it validates the full pinned /health contract — exact HTTP 200 with JSON model: "nvidia/LocateAnything-3B", ready: true, error: null (rejecting both a non-null error and an omitted/undefined error field when ready: true), and inTokenLimit: 4096 — and never spawns a local process or resolves a Python interpreter. The checkSidecarHealth() preflight in src/locator/locateanything-client.ts, used by locateUiElements() on the request path, is a lighter check: it runs within the caller's own timeoutMs and only requires HTTP success and ready: true — it does not duplicate the manager's full model/token/exact-null validation. The broker's first response after a cold start can legitimately hold for minutes; treat any other response (unreachable, non-200, malformed, wrong model/token contract, or a model-load error) as fail/report, never as a trigger to start a local sidecar, SSH into the Windows host, or manually power-manage it. Optionally set LOCATEANYTHING_BROKER_STARTUP_TIMEOUT_MS (finite positive integer, max 600000) to override the default 600000ms cold-start budget.

The TypeScript client sends image bytes with each locator request, so LOCATEANYTHING_SIDECAR_URL can point to a remote GPU service that exposes the same contract.

Local Linux launcher (archival / local-development provenance only)

The launcher below starts a LocateAnything process directly on the machine that runs it and binds only to 127.0.0.1:39731. It remains for historical/local-development reference (e.g. a non-Pi developer machine that hosts the model itself) and is not permitted on this Pi under the current broker-only topology.

It resolves an explicit LOCATEANYTHING_PYTHON without fallback, then /home/agent-runner/projects/.venvs/ui-diff-mcp-locateanything/bin/python, then python3. It likewise requires an explicit LOCATEANYTHING_EAGLE_EMBODIED_DIR, or the default /home/agent-runner/projects/Eagle/Embodied, to contain the locateanything_worker package supplied by Eagle Embodied.

# Historical/local-development only — do not run on this Pi.
# Validate Python, Eagle Embodied, and the loopback-only configuration only.
bash scripts/start-locateanything-sidecar.sh --check-only

# Historical/local-development only — do not run on this Pi.
# Start or reuse a healthy local sidecar. It prints the child PID and log path.
bash scripts/start-locateanything-sidecar.sh

The launcher accepts no host override. UI_DIFF_LOCATEANYTHING_PORT_INTERNAL is an internal diagnostic/test hook only; production uses port 39731. LOCATEANYTHING_STARTUP_TIMEOUT_MS (architecture default 600000 on ARM64/Pi / aarch64, 120000 on all other supported architectures; max 600000) and LOCATEANYTHING_STARTUP_POLL_MS (default 500, max 10000) configure how long the launcher waits for /health to report "ready": true before failing; a nonempty error is reported immediately. The longer ARM64 default exists because measured Pi Q4 cold start is about 473 seconds; a 120s default would kill a healthy Pi load. These are distinct from LOCATEANYTHING_TIMEOUT_MS, the separate Node-side inference-request timeout consumed by the MCP client for /v1/locate-ui-elements calls — it does not control launcher readiness.

LOCATEANYTHING_SKIP_MODEL=1 is only a diagnostic shortcut. It makes /health.ready true without loading the 3B model and still runs CV/OCR/optional lanes, but a run made with that flag is not full LocateAnything-model release evidence.

Legacy ReDroid Co-Location Evidence (Historical Only)

The following contract is retained to explain and test historical artifacts. Do not start ReDroid or use this section as current operator guidance or release evidence.

On the C++ backend, if a ui-diff-redroid (or UI_DIFF_LOCATEANYTHING_REDROID_NAME_INTERNAL-named) Docker/Podman container is running, the launcher refuses to start the locator unless LOCATEANYTHING_COLOCATION_EVIDENCE points to a file proving a prior measured concurrent run stayed within resource bounds. The file is key=value lines with exactly these 9 keys, no more and no fewer:

Key

Required value

schema_version

1

engine_commit

the pinned locate-anything.cpp commit

model_sha256

the pinned Q4_K model SHA-256

abi_version

1

quantization

Q4_K

host_machine

aarch64 or x86_64, matching the host

concurrent_peak_rss_kib

a positive integer

concurrent_swap_delta_kib

0

status

pass

Lines containing shell metacharacters ($(, `, ;, |, &, >, <, or quotes) are rejected without ever being evaluated as shell code; the file is only ever read with case/parameter-expansion string comparisons.

locateanything-startup.metrics, written by the launcher after the sidecar becomes ready, is a separate post-ready artifact recording that one locator process's own RSS/swap. It is not proof of concurrent ReDroid co-location and must not be substituted for LOCATEANYTHING_COLOCATION_EVIDENCE.

Parser-only sidecar tests:

/home/agent-runner/projects/.venvs/ui-diff-mcp-locateanything/bin/python -m unittest sidecars.locateanything.test_parser

Raspberry Pi Operator Commands

# Verify the dedicated Samsung identity. Use this absolute wrapper for every
# device operation; never use plain adb or implicit device selection.
/home/agent-runner/.local/bin/phone-adb devices -l
/home/agent-runner/.local/bin/phone-adb shell getprop ro.product.model
/home/agent-runner/.local/bin/phone-adb shell getprop ro.build.version.release
/home/agent-runner/.local/bin/phone-adb get-serialno

# Capture a serial-safe diagnostic screenshot.
/home/agent-runner/.local/bin/phone-adb exec-out screencap -p > /absolute/path/to/actual.png

# Check the LocateAnything broker's health directly. Never start a local sidecar on this
# Pi — the launcher scripts below are archival/local-development only.
curl --fail --silent --show-error --max-time 600 http://127.0.0.1:39731/health
# Verify the output body exactly: model: "nvidia/LocateAnything-3B", ready: true,
# error: null, inTokenLimit: 4096. Any other body (missing/non-null error, wrong
# model, wrong inTokenLimit, or a non-200/failed curl) means the broker is not
# ready/release-ready — fail/report and stop rather than proceeding.

# Fetch verified Calorix Actions APK (source-SHA + SHA256 required)
# Workflow: .github/workflows/android-build.yml (named "Build Android APK")
# Artifact: android-apk-<sha>
bash scripts/fetch-calorix-actions-apk.sh \
  --repo ia23a-lachnita/calorix \
  --source-sha 1f538641f5e5f5c4a48c95cdfb97462838187106 \
  --source-clean \
  --workflow android-build.yml \
  --artifact-name android-apk-1f538641f5e5f5c4a48c95cdfb97462838187106 \
  --output "$HOME/Downloads/calorix-release.apk"

# Verify package-lock.json root bin is dist/src/index.js
bash scripts/verify-package-bin-lock.sh

# Full repository verification with the sidecar parser environment
PATH=/home/agent-runner/projects/.venvs/ui-diff-mcp-locateanything/bin:$PATH npm run verify

Evidence boundary: Fresh auto-capture release evidence requires UI_DIFF_ADB_EXECUTABLE set to the authorized wrapper and UI_DIFF_ADB_SERIAL left unset (the wrapper already pins the serial) — or explicit adbExecutable/adbSerial options — so every ADB call routes through the authorized Samsung wrapper. For generic executables, set UI_DIFF_ADB_SERIAL to the target device serial. Manual wrapper capture is serial-safe diagnostic input, and UI_DIFF_LIVE_ACTUAL_IMAGE is an override rather than fresh auto-capture proof.

MCP Integration

Claude Code (project-scoped .mcp.json)

{
  "mcpServers": {
    "ui-diff": {
      "command": "node",
      "args": ["/home/agent-runner/projects/ui-diff-mcp/dist/src/index.js"],
      "env": {
        "OPENROUTER_API_KEY": "<your-key>"
      }
    }
  }
}

Codex (project-scoped .codex/config.toml)

[mcp_servers.ui-diff]
command = "node"
args = ['/home/agent-runner/projects/ui-diff-mcp/dist/src/index.js']
enabled = true

Live Release Gates

npm run verify is deterministic and does not call external APIs. Before production use, run the live gates:

Gate

Command

Required env

Direct Gemini models

npm run verify:gemini-live

RUN_GEMINI_LIVE=1, GEMINI_API_KEY

Direct Mistral models

npm run verify:mistral-live

RUN_MISTRAL_LIVE=1, MISTRAL_API_KEY

OpenCode Zen MiMo

npm run verify:opencode-live

RUN_OPENCODE_LIVE=1; optional OPENCODE_API_KEY

Free OpenRouter models

npm run verify:free-live

RUN_FREE_LIVE=1, OPENROUTER_API_KEY

Native NVIDIA models

npm run verify:nvidia-live

RUN_NVIDIA_LIVE=1, NVIDIA_API_KEY

Full pipeline

npm run verify:mcp-live

RUN_UI_DIFF_LIVE=1, LOCATEANYTHING_SIDECAR_URL; provider keys optional fallbacks

Bounded Calorix smoke

npm run verify:calorix-live

RUN_CALORIX_UI_DIFF_LIVE=1, canonical expected reference, sidecar, dedicated Samsung; serial-safe auto-capture plumbing is implemented — set UI_DIFF_ADB_EXECUTABLE to the authorized wrapper with UI_DIFF_ADB_SERIAL unset; fresh physical-phone evidence from the authorized Samsung through this route is still pending

Full Calorix all-target

npm run verify:calorix-full-live

RUN_CALORIX_FULL_LIVE=1, canonical expected reference, sidecar, dedicated Samsung; do not set UI_DIFF_MAX_AUDIT_PAIRS; serial-safe auto-capture plumbing is implemented — set UI_DIFF_ADB_EXECUTABLE to the authorized wrapper with UI_DIFF_ADB_SERIAL unset; fresh physical-phone evidence from the authorized Samsung through this route is still pending

Bounded Smoke vs Full Classification

A bounded smoke run (UI_DIFF_MAX_AUDIT_PAIRS set) is explicitly not full visual classification:

  • auditLimited: true in compact output and report.json.

  • visualClassificationStatus: "incomplete" unless all pairs happened to be within the limit.

  • auditScope.auditedPairs / auditScope.totalPairs records the actual vs total pair count.

A full all-target run must show auditLimited: false. Use verify:calorix-full-live to confirm.

See docs/release/production-readiness-checklist.md for the complete sign-off sequence.

Available Tools

7 tools
capture_mobile_screenB

Captures a screenshot from a connected mobile device using adb or ios-simctl.

ParametersJSON Schema
NameRequiredDescriptionDefault
targetYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
captureYes
imagePathYes

TDQS

B3.3/5.0
Behavior2/5

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

With no annotations, description should disclose behavioral traits but only mentions capture action. No info on side effects, output location, or required permissions.

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?

Single sentence, 12 words, efficient and front-loaded with the core action and methods.

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

Completeness3/5

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

Given output schema exists (not shown), description need not detail return values, but still lacks setup context (device connection) and output format. Adequate for a simple tool but minimal.

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 0%, and description adds context by linking the target parameter to the capture methods (adb/ios-simctl), but does not explain implications like Android vs iOS separation.

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?

Clearly states the tool captures a screenshot from a connected mobile device using adb or ios-simctl. Differentiates from sibling tools like compare_ui_images, which focus on analysis rather than capture.

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

Usage Guidelines2/5

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

No guidance on when to use this tool versus alternatives. Lacks context about prerequisites (e.g., device must be connected) or when not to use it.

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

compare_ui_imagesC

Compares an expected mobile mockup image against an actual mobile screenshot using only deterministic methods (no visual models).

ParametersJSON Schema
NameRequiredDescriptionDefault
modeNofree
runLabelNo
projectRootNo
actualImagePathYes
expectedImagePathYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
runIdYes
statusYes
summaryYes
warningsYes
diffCountYes
auditScopeNo
reportPathYes
artifactRootYes
auditLimitedYes
debugSummaryNo
runArtifactsYes
recoverySummaryNo
locatorCoverageStatusYes
unresolvedRegionCountYes
visualClassificationStatusYes

TDQS

C2.6/5.0
Behavior2/5

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

With no annotations, the description must cover behavioral traits. It states 'deterministic methods' and 'no visual models', but omits side effects, permissions, performance, or behavior on mismatched images.

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 a single concise sentence that immediately states the action. However, it sacrifices completeness for brevity, lacking structure to cover parameters or context.

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

Completeness2/5

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

Given 5 parameters, an output schema, and sibling tools indicating a workflow, the description is insufficient. It doesn't explain the role of parameters or how this fits into the larger tool suite.

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

Parameters1/5

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

The input schema has 5 parameters including an enum for 'mode', but the description gives no information about any parameter. With 0% schema description coverage, this is a critical gap.

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?

The description clearly states the tool compares expected and actual mobile images using deterministic methods. It does not explicitly differentiate from sibling tools like 'discover_ui_diffs' or 'start_ui_diff_run', but the mention of 'no visual models' hints at its specificity.

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

Usage Guidelines2/5

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

The description only notes that it uses deterministic methods, implying a use case where visual models are not wanted. No guidance on when to use this over siblings or prerequisites (e.g., captured images). Lacks explicit context.

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

discover_ui_diffsC

Runs full UI diff analysis, including visual model-based target discovery and classification. Compares expected mockup against actual screenshot.

ParametersJSON Schema
NameRequiredDescriptionDefault
modeNofree
runLabelNo
projectRootNo
actualImagePathYes
expectedImagePathYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
runIdYes
statusYes
summaryYes
warningsYes
diffCountYes
auditScopeNo
reportPathYes
artifactRootYes
auditLimitedYes
debugSummaryNo
runArtifactsYes
recoverySummaryNo
locatorCoverageStatusYes
unresolvedRegionCountYes
visualClassificationStatusYes

TDQS

C2.7/5.0
Behavior2/5

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

No annotations are provided, so the description carries full burden. It mentions visual model-based discovery but lacks details on side effects, duration, permissions needed, or output summary (despite existence of output schema).

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

Conciseness3/5

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

The description is two sentences (20 words) and front-loads the main action, but it could be more structured by enumerating inputs or output. It is concise but lacks completeness.

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

Completeness2/5

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

Given the tool has 5 parameters including an enum, and an output schema, the description is inadequate. It does not mention return values or how the analysis integrates with sibling tools (e.g., read_ui_diff_report).

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

Parameters1/5

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

Schema description coverage is 0%, meaning no parameter descriptions. The description only hints at expectedImagePath and actualImagePath via 'compares expected mockup against actual screenshot', but omits mode, runLabel, and projectRoot entirely, failing to compensate for the lack of schema descriptions.

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 clearly states the tool runs full UI diff analysis including visual model-based target discovery and classification, and compares expected mockup against actual screenshot. This distinguishes it from simpler comparison tools like compare_ui_images.

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

Usage Guidelines2/5

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

No guidance is provided on when to use this tool versus its siblings (e.g., compare_ui_images, start_ui_diff_run). There is no mention of prerequisites, exclusions, or alternative tools for specific scenarios.

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

get_ui_diff_run_statusB

Returns the status of a background UI diff run started by start_ui_diff_run.

ParametersJSON Schema
NameRequiredDescriptionDefault
runIdYes
projectRootYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
errorNo
labelNo
runIdYes
statusYes
progressNo
startedAtNo
reportPathNo
completedAtNo
heartbeatAtNo
artifactRootNo
checkpointPathNo

TDQS

B3.3/5.0
Behavior2/5

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

No annotations are provided, and the description only states the basic purpose. It does not disclose that this is a read-only operation, any authentication needs, rate limits, or potential error states. Given the lack of annotations, the description should provide more behavioral context.

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 a single concise sentence that clearly states the core function. It front-loads the purpose with no wasted words, but could benefit from a second sentence providing additional context.

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

Completeness3/5

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

With 2 required parameters and an output schema present, the description is minimally complete: it states the function but does not elaborate on the status values or the context of the run. The output schema reduces some burden, but the agent lacks guidance on interpreting the response.

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

Parameters2/5

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

The input schema has 0% description coverage; parameters 'runId' and 'projectRoot' have only type and minLength constraints. The description adds no additional meaning to these parameters, so the agent must infer their purpose from the tool name alone.

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 clearly states it returns the status of a background UI diff run, with a specific verb 'Returns' and resource 'status of a background UI diff run started by start_ui_diff_run'. This distinguishes it from siblings like start_ui_diff_run (starting) and read_ui_diff_report (reading the report).

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?

The description implies usage after starting a run via start_ui_diff_run, but does not explicitly provide when to use or not use this tool, nor does it mention alternatives or prerequisites. It relies on context from the sibling name.

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

read_ui_diff_reportB

Reads and returns a previously generated UI diff report JSON file.

ParametersJSON Schema
NameRequiredDescriptionDefault
reportPathYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
reportYes

TDQS

B3.4/5.0
Behavior3/5

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

The description discloses that the tool reads and returns JSON data, implying a non-destructive read operation. However, with no annotations, it carries full burden and lacks details on error handling (e.g., if the file does not exist) or permissions. It is minimally adequate but not comprehensive.

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 a single sentence with no wasted words. It is front-loaded and immediately conveys the tool's action and subject.

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

Completeness3/5

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

The tool is simple with one parameter and an output schema. The description covers the basic purpose but lacks workflow context—e.g., that the report path likely comes from a previous step like 'start_ui_diff_run'. It does not mention the output schema existence or how to interpret results. Adequate for a basic tool but could be more helpful.

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

Parameters2/5

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

Schema coverage is 0%, so the description must compensate. It adds no meaning to 'reportPath' beyond the name and type (string). The description does not specify the path format, how to obtain it, or constraints. The parameter is self-explanatory in name, but the tool description fails to provide useful context.

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 clearly states the tool reads and returns a previously generated UI diff report JSON file. The verb 'reads and returns' and resource 'previously generated UI diff report' are specific, and it is distinct from sibling tools (e.g., 'capture_mobile_screen', 'start_ui_diff_run').

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

Usage Guidelines2/5

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

The description provides no guidance on when to use this tool versus alternatives. It does not mention prerequisites (e.g., a report must already exist) or that this should be used after starting a diff run. No exclusions or workflow context are given.

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

start_ui_diff_runA

Starts a UI diff run in the background without holding the MCP request open. Returns a runId to poll with get_ui_diff_run_status. Use this for large or slow audits like Calorix full scans.

ParametersJSON Schema
NameRequiredDescriptionDefault
modeNofree
labelNo
projectRootNo
resumeRunIdNo
actualImagePathYes
expectedImagePathYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
runIdYes
statusYes
messageYes

TDQS

A3.6/5.0
Behavior3/5

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

With no annotations provided, the description must disclose all behavioral traits. It correctly states the tool runs in the background and is non-blocking, but omits details such as authentication needs, rate limits, or side effects like creating persistent state.

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 extremely concise at two sentences, with each sentence providing distinct value: the first states the action and key behavior, the second gives a usage suggestion. No wasted words.

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

Completeness3/5

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

Given the tool's complexity (6 parameters, async) and the presence of an output schema, the description covers the async nature and return value adequately. However, it lacks explanation of parameters, required vs optional, and how this tool differs from synchronous siblings like compare_ui_images.

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

Parameters1/5

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

Schema description coverage is 0%, and the description does not mention any parameters or their roles. The required paths (expectedImagePath, actualImagePath) are not explained, nor is the mode enum or other optional fields. This provides no assistance to the agent beyond the schema itself.

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 clearly states the tool starts a UI diff run asynchronously, returns a runId, and distinguishes from sibling tools like get_ui_diff_run_status and compare_ui_images by emphasizing background execution for large/slow audits.

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?

The description explicitly recommends use for large/slow audits and mentions the workflow of polling with get_ui_diff_run_status. However, it does not explicitly state when not to use this tool or mention alternatives like compare_ui_images for smaller diffs.

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

ui_diff_model_healthA

Checks health of all visual models used by the diff pipeline.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultsYes
checkedAtYes

TDQS

A3.5/5.0
Behavior2/5

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

No annotations are provided, so the description bears full responsibility for behavioral disclosure. It does not state whether the tool is read-only, what happens if models are unhealthy, or any side effects. For a health check, it should at least indicate it has no destructive actions, but this is missing.

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 a single, clear sentence with no wasted words. It is front-loaded and gets straight to the point. Ideal conciseness.

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

Completeness3/5

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

An output schema exists, so explaining return values is not required. However, the description is very terse and does not provide any context beyond the basic function. For a health check, one might expect additional context like what constitutes health, but given the simplicity, it is minimally adequate.

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 has zero parameters, so the baseline is 4. The description does not need to elaborate on parameters. It could have explained what 'health' means, but since there are no parameters, a score of 4 is appropriate.

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 clearly states it checks the health of all visual models used by the diff pipeline. It is a specific verb ('checks') and resource ('health of all visual models'), and it is distinct from sibling tools which focus on capturing screens, comparing images, discovering diffs, etc. No ambiguity.

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

Usage Guidelines2/5

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

The description provides no guidance on when to use this tool versus alternatives, such as before starting a diff run or after an issue. There is no mention of prerequisites, conditions for use, or exclusions. It only states what it does, not when to use it.

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. 7 tool updatesv0.1.0
    • First observedcapture_mobile_screen
    • First observedcompare_ui_images
    • First observeddiscover_ui_diffs
    • First observedget_ui_diff_run_status
    • First observedread_ui_diff_report
    • First observedstart_ui_diff_run
    • First observedui_diff_model_health

TDQS

B3.2/5.0

Scored across 7 tools

Disambiguation4/5

Tools are mostly distinct but compare_ui_images and discover_ui_diffs both compare screenshots; descriptions clarify deterministic vs model-based, but could cause temporary confusion.

Naming Consistency2/5

Naming is inconsistent: some tools start with verbs (capture, compare, discover, get, read, start) while ui_diff_model_health is a noun phrase. Also mixing ui_diff and ui_diffs, and get vs read.

Tool Count5/5

7 tools is well-scoped for UI diffing: capture, two compare modes, background run management (start, status, report), and health check. No unnecessary tools.

Completeness4/5

Covers core workflow: capture, compare (sync/async), background run, report retrieval, model health. Minor gap: no tool to list or cancel runs, but not critical.

Maintenance

ActivityActive
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers