Agy Ui Mcp — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Agy Ui Mcp (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
An MCP (Model Context Protocol) server that delegates frontend / UI work to Google Antigravity's `agy` CLI (Gemini) - while guaranteeing the agent never touches backend, API, or business logic. It is designed to be shared by Claude Code and Codex as a dedicated "FE/UI worker".
The server exposes two tools:
prompt agy to edit CSS/components toward the target design, diff-gate the result to revert anything out of scope, re-screenshot, and repeat until it converges (or hits max_iters). Edits are applied to your working tree.
(route × device × theme × state), optionally run accessibility checks, and have agy critique it read-only (any edit agy makes is reverted).
| Surface | Platform values | How it's captured |
|---|---|---|
| Web apps | web (default) | Playwright (Chromium) over the dev-server URL |
| Mobile web-targets | expo-web, ionic, flutter-web | Playwright (same as web) |
| Native iOS | ios-sim | flutter run on the iOS Simulator + xcrun simctl screenshots |
| Native Android | android-emu | flutter run on an Android emulator + adb screenshots |
Across these it supports responsive viewports, device emulation, dark mode / prefers-color-scheme, forced-colors (high contrast), print media, RTL, component states (via pre_steps), seeded localStorage, per-target design references, and match-score convergence when design references are provided.
Accessibility: for web targets, ui_review injects the vendored axe-core into the page and returns structured WCAG violations (per target), which also ground agy's critique.
The case this server is built for: you (or Claude Code / Codex) shipped a full-stack project - backend and frontend both done - but the FE drifted from, or doesn't match, the original design (screen mockups, or design tokens with an HTML/CSS demo). You want to redo the FE to match the design without risking the working backend. That is exactly what the diff-gate guarantees: agy realigns the UI, and anything outside your FE allow scope (API, server, business logic) is reverted automatically.
What it's strong at vs. where it needs help - this is an iterative refinement loop, not a from-scratch FE generator:
| Your FE today | Fit |
|---|---|
| Structure is right, styling/layout/colors/spacing/responsive is off | Great - its core job; realistically ~80-90% then human polish |
| Partly wrong (a few components / screens drifted) | Good - run it screen by screen with the matching design_ref |
| Structurally wrong (wrong component tree, missing screens, wrong layout) | Partial - it nudges existing code toward the design within scope; it does not rebuild markup from scratch. Have Claude Code/Codex scaffold the correct structure first, then use this server to drive pixel fidelity |
Fidelity is highest when you provide an HTML/CSS demo or design tokens (exact colors/spacing/fonts) rather than an image alone (values are inferred from pixels). Note the convergence score is agy's own visual self-assessment - always eyeball the returned shots_before/shots_after and diff to sign off.
Workflow
preserved; the only requirement is a git repo with ≥1 commit).
.agy-ui-scope that allows only FE files and **denies thebackend, declares how to serve the app, and lists one target per screen** with that screen's design_ref (see below).
shots_before/shots_after + diff, then iterate (max_iters).
agy critique what's left andsurface WCAG issues.
reach within scope.
Sample config - a Vite/React app, realigned screen-by-screen against mockups in ./design/:
model: "gemini-3.5-flash"
platform: web
# FE surface agy may edit/create.
allow:
- "src/**/*.css"
- "src/**/*.scss"
- "src/components/**"
- "src/**/*.tsx"
- "index.html"
# Backend / logic - always reverted, even if agy edits them.
deny:
- "**/api/**"
- "**/server/**"
- "**/*.server.*"
- "**/route.*"
# Sensitive entry points - reverted AND reported for a human to decide.
ambiguous:
- "src/main.tsx"
- "src/App.tsx"
- "vite.config.*"
serve:
cmd: "npm run dev"
url: "http://localhost:5173"
ready_timeout: 30
devices:
desktop: { width: 1440, height: 900 }
mobile: { name: "iPhone 13" } # full Playwright device emulation
# One capture per screen, each matched against its own design mockup.
# (targets supersedes the simple `viewports` list when present.)
targets:
- name: "home-desktop"
route: "/"
device: "desktop"
design_ref: "./design/home-desktop.png" # image OR an HTML/CSS demo render
- name: "dashboard-desktop"
route: "/dashboard"
device: "desktop"
design_ref: "./design/dashboard-desktop.png"
- name: "settings-mobile-dark"
route: "/settings"
device: "mobile"
color_scheme: "dark" # emulate prefers-color-scheme: dark
design_ref: "./design/settings-mobile-dark.png"Then drive each screen, e.g. ui_implement(project_dir=".", task="Match this screen to its design_ref", target_route="/dashboard"). Targets carry the per-screen mockup; target_route picks which one to work on.
The server runs on macOS and Linux. It spawns agy (and native flutter run) through a Unix pseudo-terminal (pty) and manages process groups with POSIX-only calls, so native Windows is not supported - run it under WSL2 (Windows Subsystem for Linux) instead.
| OS | Web + mobile web-targets | Native Android | Native iOS |
|---|---|---|---|
| macOS | yes | yes | yes |
| Linux | yes | yes | no (iOS needs macOS + Xcode) |
| Windows (native) | no | no | no |
| Windows via WSL2 | yes | with adb/emulator setup | no |
Notes:
agy (and log in) and runplaywright install chromium inside WSL. Web and mobile web-targets work out of the box; native Android additionally needs adb/emulator wiring (e.g. connecting to a Windows-side emulator over TCP, or running the emulator inside WSL2).
pty layer with ConPTY(e.g. pywinpty) and the POSIX process-group calls; it is not implemented.
agy is run as agy -p "<prompt>" through a Pythonpseudo-terminal (pty.openpty + subprocess.Popen), because agy drops its stdout when attached to a non-TTY pipe. Output is captured from the PTY master; ANSI escapes and carriage returns are stripped.
agy authenticates via your existing Geminisubscription/login - no GEMINI_API_KEY is passed by this server.
agy.Web runs happen in a throwaway git worktree; after each turn the server classifies every changed path against your scope (deny > ambiguous > allow > default-deny) and reverts anything not allowed (ambiguous paths are reverted and reported as escalations). A staged edit is restored from the baseline, not the index, so it cannot slip through.
Playwright, embeds those paths in the prompt (agy opens them with its own read_file tool - there is no image flag), lets agy edit, applies the diff-gate, re-screenshots, and loops.
build cache). flutter run is launched under a PTY and hot-reloaded (r) between iterations - with an automatic hot-restart (R) fallback when a reload produces no visual change. A graceful quit (q) lets Flutter release its lockfile cleanly.
snapshots your project's current state into a dangling git baseline commit (without touching your index/HEAD/worktree) and records your pre-existing untracked files. The diff-gate and reverts compare against that baseline, so only `agy`'s edits are gated/undone and your uncommitted work is preserved exactly - you do not need to commit or stash first. The only hard requirement is that the project is a git repo with at least one commit; if it isn't, the tool returns a structured {"status": "blocked", ...} result explaining how to fix it (e.g. git init) instead of running unprotected.
(or set AGY_UI_CHROME_CHANNEL=chrome to reuse an already-installed Chrome); not needed for native-only use
absent. Run ui_init to generate one, and see templates/agy-ui.rule.md for the recommended agent rule block
ios-sim): macOS + Xcode + a Flutter project, and a bootediOS Simulator
android-emu): the Android SDK platform-tools (adb) andan AVD; the adapter can auto-launch the AVD by name (emulator -avd <name>)
Runs a self-contained installer straight from the internet (no clone needed). It installs the server, tries to install Chromium, and offers to register with Claude Code:
curl -fsSL https://raw.githubusercontent.com/qdzsh/agy-ui-mcp/main/scripts/bootstrap.sh | bash# 1. Install the server (gives you an `agy-ui-mcp` command on PATH)
pipx install agy-ui-mcp
# or: uv tool install agy-ui-mcp
# 2. Register it with Claude Code
claude mcp add agy-ui --scope user -- agy-ui-mcpTo install the latest unreleased changes straight from the repo:
pipx install git+https://github.com/qdzsh/agy-ui-mcp
# or: uv tool install git+https://github.com/qdzsh/agy-ui-mcpThe Chromium browser auto-installs on first use, so there is no manual playwright install chromium step. (To reuse an already-installed Chrome and skip the download, set AGY_UI_CHROME_CHANNEL=chrome.)
git clone https://github.com/qdzsh/agy-ui-mcp.git && cd agy-ui-mcp
./scripts/install.sh # installs the package + Chromium, and offers to
# register with Claude Codescripts/install.sh is interactive and idempotent; re-run it any time.
You do not need a .agy-ui-scope file to get going - the server auto-detects your stack (Vite/React, Next.js, Expo, Ionic, CRA, Flutter-web, or generic web) and synthesizes a scope on the fly. The minimal flow:
./design/home.png. ui_implement(project_dir=".", task="Match the running home screen to this mockup",
design_refs=["./design/home.png"], target_route="/")That is enough for the loop to run with zero config files. When you want to customize the scope (allow/deny globs, per-screen targets, serve command), call ui_init(project_dir=".") once to detect your stack and write a real, inspectable .agy-ui-scope you can edit (it never overwrites an existing one unless overwrite=True). See templates/agy-ui.rule.md for a copy-paste rule block that teaches your agent how to use this MCP correctly.
The Chromium browser auto-installs on first use (a one-time download; no manual playwright install chromium step). Set AGY_UI_CHROME_CHANNEL=chrome to reuse an already-installed Chrome and skip that download entirely.
# If installed as a console script (pipx / uv tool / pip):
claude mcp add agy-ui --scope user -- agy-ui-mcp
# Or run the module directly (e.g. from an editable/venv install):
claude mcp add agy-ui --scope user -- python -m agy_ui_mcpAdd to ~/.codex/config.toml:
[mcp_servers.agy-ui]
command = "agy-ui-mcp" # or: command = "python", args = ["-m", "agy_ui_mcp"]A .agy-ui-scope file is optional - without one the server auto-detects your stack and synthesizes a scope (see "Zero-config quick start" above). Add a real file only when you want to customize the allow/deny globs, serve command, or per-screen targets. The easiest way is ui_init(project_dir="."), which detects your stack and writes a starter .agy-ui-scope you can then edit.
Alternatively, copy the fully annotated template and edit it for your stack:
cp .agy-ui-scope.example /path/to/your/app/.agy-ui-scopeA minimal web scope:
# platform: web # default; also expo-web / ionic / flutter-web / ios-sim / android-emu
allow:
- "src/**/*.css"
- "src/components/**"
deny:
- "src/api/**" # backend - agy edits here are always reverted
- "**/*.server.*"
ambiguous:
- "src/main.tsx" # reverted AND reported for a human to decide
serve:
cmd: "npm run dev"
url: "http://localhost:5173"
ready_timeout: 30
viewports: [1440, 768, 390]
model: "gemini-3.5-flash"A native (iOS) scope uses targets + a device registry instead of viewports:
platform: ios-sim
serve:
cmd: "flutter run -d <simulator-udid>" # argv-split (no shell) for native
url: ""
ready_timeout: 600 # first Xcode/gradle build is slow
allow: ["lib/main.dart"]
deny: ["lib/data.dart"]
devices:
sim: { name: "iPhone 17" } # or udid: "..."
targets:
- { name: order-mobile, device: sim }
model: "gemini-3.5-flash"See .agy-ui-scope.example for the full set of options (per-target design_ref, theme, rtl, color_scheme, forced_colors, media, full_page, local_storage, pre_steps, serve.reload_cmd, etc.).
Zero-config: every tool works with no .agy-ui-scope present - the server auto-detects your stack (Vite/React, Next.js, Expo, Ionic, CRA, Flutter-web, or generic web) and synthesizes a scope for the run.`ui_init(project_dir=".", overwrite=False)` -> detects your stack and writes a starter .agy-ui-scope (never clobbers an existing one unless overwrite=True). Returns status (ok/exists/error), scope_path, written, detected ({framework, platform, serve_cmd, serve_url, package_manager}), allow, deny, design_dir_found, next_steps, and warnings.
`ui_implement(project_dir, task, design_refs=None, target_route=None, max_iters=4, apply=True, match_threshold=90)` → returns files_changed, diff, escalations, iterations, shots_before/shots_after, targets, applied/applied_files, match_score, match_gaps, warnings. When apply is true the surviving in-scope edits are written to your working tree.
`ui_review(project_dir, target_route=None, against_design=None, a11y=True)` → returns critique, shots, targets, a11y ({target: [violations]}), warnings. Read-only.
Both may instead return {"status": "blocked", "blocked_reason": "...", ...} when a native/in-place run can't be made safe (non-git or no commit yet) - the blocked_reason tells you exactly what to do.
safety snapshot). A dirty working tree is fine and is preserved; only a non-git or commit-less project is refused (with a clear message). Web runs use an isolated worktree and also require git.
browser; the server installs it automatically on first use (a one-time download, with a short stderr notice). Set AGY_UI_CHROME_CHANNEL=chrome to reuse an installed Chrome/Edge and skip the download, or AGY_UI_NO_BROWSER_AUTOINSTALL=1 to opt out. Native-only use needs no browser.
shlex.split, no shell) so the PTYcan deliver r/R/q keystrokes to flutter directly - shell features (&&, env-var expansion, cd) in serve.cmd won't work for native.
serve.ready_timeout generously (e.g. 300-600s).
flutter run reports a missing ios/ orandroid/ project, regenerate it with flutter create --platforms=ios . (or android).
| Variable | Effect |
|---|---|
AGY_UI_CHROME_CHANNEL | Use an installed browser channel (e.g. chrome or msedge) instead of Playwright's bundled Chromium. Skips the one-time Chromium download. |
AGY_UI_CHROME_EXECUTABLE | Explicit path to a Chromium-based browser executable to launch. |
AGY_UI_NO_BROWSER_AUTOINSTALL | Set to a truthy value to disable the automatic playwright install chromium on first use. |
AGY_UI_DRY_RUN | Skip every external side effect and return a stub payload (see below). |
Set AGY_UI_DRY_RUN=1 to make the tools skip every external side effect (spawning agy, launching Playwright, running git, starting a dev server) and return a stub payload. The package imports and the scope/diff-gate logic unit-test without agy, Playwright browsers, or a running dev server.
pip install -e ".[dev]" && python -m pytest -qMIT.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.