averi

mcp
Security Audit
Fail
Health Warn
  • License — License: MIT
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Low visibility — Only 5 GitHub stars
Code Fail
  • exec() — Shell command execution in docs/truth/live-records/a9-fill-tail-diag/diag.mjs
  • exec() — Shell command execution in docs/truth/live-records/android-forms-eyes.json
Permissions Pass
  • Permissions — No dangerous permissions requested

No AI report is available for this listing yet.

SUMMARY

MCP server for verifying mobile app changes on iOS Simulators and Android Emulators

README.md

averi — on-device verification for AI coding agents

averi is an MCP server that gives a coding agent hands on iOS Simulators and Android Emulators: launch the app, tap, type, read the screen, assert, screenshot. Its differentiator is ensure_state — the project checks in an averi.yaml describing app states (like logged in) and how to reach them, so the agent gets past login and deep into the app deterministically, instead of fumbling through it tap by tap on every task.

averi itself is project-independent: it ships only tools. Everything app-specific — states, flows, credentials — lives in your app repo.

Installation

averi is on npm — nothing to clone or build. Setup is a job for your agent, not for you:

1. Register the MCP server (in your app repo):

claude mcp add averi -- npx -y averi

2. Restart your agent session and paste this prompt:

Set up the averi tool in this repo. Fetch https://raw.githubusercontent.com/mholecy/averi/main/SETUP.md and follow it step by step — run every check, and don't report done until the final verification passes.

The agent handles the rest: verifies prerequisites (adb, idb, a booted device), creates the gitignored .env.averi for test credentials, installs the agent skill, authors averi.yaml by driving your actual app, and proves the setup by logging in end to end — twice (once cold through the full flow, once warm in ~1 s).

Prefer to do it by hand, or setting up for a whole team (committed .mcp.json)? Everything is in SETUP.md — it reads fine for humans too.

Updating later is also an agent job — ask it to follow SETUP.md → Updating averi (bumps the version, re-syncs the copied skill so it can't drift, re-runs the login proof).

What is averi

flowchart LR
    A(["AI agent"]) -- MCP --> S["averi server"]
    S -- "adb / simctl + idb (opt-in WDA)" --> D["emulator / simulator"]
    subgraph R["your app repo"]
        Y["averi.yaml<br/>states, flows, selectors"]
        E[".env.averi — gitignored<br/>credential values"]
    end
    S -. reads .-> Y
    S -. reads .-> E

Your agent changes mobile code all day but can't see whether the app actually works. averi closes that loop: after a change, the agent builds the app, installs it, drives it to the changed screen, and verifies — on both platforms, on real simulators.

Three ideas make that reliable:

  • One tree, one selector language. Both platforms' accessibility trees are normalized into one model, so the same selectors (id:, text:"…", role:) — and usually one yaml — drive both OSes, with per-platform overrides only where they differ.
  • States over tap sequences. averi.yaml declares where the app can be and how to get there. The agent says ensure_state("logged_in") and averi does the rest, deterministically.
  • Credentials never in yaml. ${ENV_VAR} references resolve from a gitignored .env.averi (real env vars win, so CI works normally), and values are redacted (***) from every trace and error.

How it works

ensure_state — the core loop

flowchart LR
    A["ensure_state('logged_in')"] --> B{"screen matches<br/>the state's detect condition?"}
    B -- yes --> C(["done — ~1 s"])
    B -- no --> D["run the next<br/>reach: flow, e.g. login"]
    D --> E{"detect: matches now?"}
    E -- yes --> C
    E -- no --> G{"another reach: flow?"}
    G -- yes --> D
    G -- no --> H["re-run the repeatable<br/>rungs once (recovery)"]
    H --> I{"detect: matches now?"}
    I -- yes --> C
    I -- no --> F(["fail — error carries the trace"])

Idempotent — the agent calls it freely; it costs ~1 second when the app is already there.

reach: is a ladder, so order it cheapest-first: detect is re-checked after every flow and the rest are skipped once it matches. reach: [dismiss_post_login_prompts, login] reads as "try the cheap idempotent dismissal; fall back to a full login only if that did not get us there" — and that is what it does, including when the cheap flow fails (its tap: times out because the interstitial was not there). A failed rung escalates to the next one and is reported in the trace as ⚠ reach <flow> — unless it failed on a config mistake (an undeclared or empty credential, an unknown state or flow), which ends the call at once rather than escalating into a costlier rung. Only the last rung's failure fails the call — and not before averi re-checks detect (a flow can reach the state and then die on a later step) and, again unless the failure was a config mistake, runs the recovery pass below.

The ladder also runs backwards once, at the end. The last rung's own aftermath can produce a screen that an earlier rung exists to clear — a login completes, then a network-gated biometrics interstitial arrives too late for that login's optional: windows — and a forward-only ladder would fail there while an immediately repeated call succeeded. So whenever the ladder ends short of the state — its final wait times out, or the last rung itself throws, which is what happens when a login flow spells its success criterion as its own trailing wait: { state: ... } — averi re-runs the earlier rungs once (↻ recovery in the trace) and re-checks. Once per tool call, not per state: nested requires: and a verify leg's state and flow (whose requires: may ladder again) share that one pass. Only rungs it can prove are repeatable take part: a launch { clearState: true } anywhere in a flow, or reachable through its requires:, or a flow marked destructive: true, is never re-run: the recovery pass adds no wipes to what the ladder already spent. If the state is still not reached, the call fails with the original error — the wait's timeout, or the last rung's own failure, whichever ended the ladder.

One more guard sits in front of every destructive rung (launch { clearState: true }, or a flow marked destructive: true): if the screen could not be read — iOS idb stuck on a 0×0 tree, a cold launch's bare decor or splash — averi looks again for up to 20 s (a bare screen gets that second look before a cheap rung too), and if it still sees nothing rendered it refuses the rung (⛔ reach in the trace, Refused to run reach flow … as the error) rather than wipe an app whose screen it never saw. Nothing was wiped; retry once the screen has rendered.

The yaml is code

States and flows live in your repo and evolve with your app — when navigation changes and a flow times out, the agent fixes the descriptor as part of the change. You don't hand-author it either: the agent bootstraps it by driving your app (SETUP.md, step 4).

credentials:
  password: ${APP_PASSWORD}             # the value comes from .env.averi

states:
  logged_in:
    detect: { element: { text: "Accounts" } }
    reach: [login]

flows:
  login:
    steps:
      - launch: { clearState: true }
      - tap: { text: "Log in" }
      - type: { value: $password }        # the credential above — never a literal
      - wait: { state: logged_in, timeout: 20s }

The schema covers the real world: per-platform steps, PIN keypads without resource-ids, auto-advancing OTP boxes, branch: for state-dependent paths, optional: for dismissable interstitials.

Verification is tiered — cheapest first, numbers over impressions

Tier Tool What it answers
1 element assert Is the element there, with this text/error? Deterministic, no vision.
2 screenshot Does it look right? The agent judges with its own vision.
3 baseline pixel-diff Did anything visually regress since last time?
4 rect / color / ocr asserts, layout contracts Is the margin 24pt, the fill #FDFDFD, the rendered copy CONTINUE? Geometry and color are arithmetic — never eyeballed.

verify runs the same state/flow/asserts on both platforms and returns paired screenshots; with a layout contract it appends per-anchor geometry, color (CIEDE2000) and text/type-size parity tables. A flow or ensure_state that succeeds, every assert call (failed asserts included) and every verify leg whose state and flow completed report appAlive (when averi.yaml names the app for that platform) — false with a crash-log excerpt if the app died, unknown when the device could not be asked; a failed flow, state or leg carries its trace instead, so check get_logs for a crash. Full detail: docs/verification.md.

One habit that pays off immediately: stable ids

averi can only select what the accessibility tree exposes. Give new screens test identifiers — Modifier.testTag("login_submit") on Android Compose (with testTagsAsResourceId = true at the semantics root), .accessibilityIdentifier("login_submit") on iOS — and flows become locale-proof and asserts component-precise. Make it part of the definition of done; it also improves real accessibility tooling. (React Native on iOS needs one extra yaml line — see SETUP.md.)

Requirements

Node 20+. Android: adb + a running emulator (macOS/Linux/Windows). iOS: macOS with Xcode, a booted simulator, and idb — Apple ships simulators only with Xcode. The opt-in app.ios.treeSource: wda reads the tree through WebDriverAgent, which averi builds with xcodebuild from the appium-webdriveragent package — an optional dependency installed with averi (npx included); if that optional install was skipped or failed (e.g. --omit=optional), treeSource: wda fails with a message naming the package. Exact install commands and checks: SETUP.md, step 0.

On a Linux/Windows box you get the full Android toolset — pass platforms: ["android"] to verify (its default runs both platforms; iOS tools error only when called). OCR-backed checks use the macOS Vision framework: elsewhere ocr asserts fail closed and the text/type-size parity tables compare from the accessibility tree instead. Windows is untested — reports welcome.

Documentation

Doc For What's in it
SETUP.md your agent (and you) Step-by-step setup with verification checks
skill/SKILL.md the agent, every session Golden path, short rules, the averi.yaml reach ladder; references/ hold the full yaml reference, the full behaviour of each tool whose description was shortened, and a troubleshooting guide by symptom
docs/verification.md reference Assert semantics, forms, layout/color/text contracts
ARCHITECTURE.md contributors Design, adapters, tool surface, roadmap

Development

npm install
npm test           # vitest
npm run lint       # typecheck src + tests
npm run mutate     # mutation run over the pure leaves' tests (ARCHITECTURE.md §8)
npm run build      # tsc → dist/
npm run dev        # run the MCP server over stdio from source

Layout (each layer imports only those after it): src/mcp/ (tool surface, no logic) · src/run/ (tool commands, verify runs) · src/flow/ (yaml schema, credentials, engine) · src/interact/ (tap/type/fill/scroll/swipe, keyboard guard) · src/verify/ (asserts, baselines, pixel poll, parity tables, crash scan) · src/ui-tree/ (selectors, geometry, tree verdicts) · src/adapters/ (adb, simctl/idb, opt-in WDA tree source, one normalized tree) · src/util/ · skill/ · docs/plans/.

Reviews (0)

No results found