Labsco
hoainho logo

podium-mcp

β˜… 2

from hoainho

Unified mobile E2E MCP β€” 28 tools (mobile-mcp + Maestro + RN-debugger parity)

πŸ”₯πŸ”₯πŸ”₯βœ“ VerifiedFreeQuick setup

podium-mcp

One baton. Every instrument.

A single MCP stdio endpoint with 51 tools for iOS (simulator + real) and Android device control, native UI automation, end-to-end flows, trustworthy assertions, React Native debugging, WebView DOM + network inspection, and a no-vision canvas/WebGL brain for Pixi/Konva/Fabric/Phaser/Three/Babylon (validated live in WebKit) β€” plus an experimental engine bridge for instrumented Unity/GL builds (AltTester) β€” one connection instead of half a dozen servers.

Glama

-2ea44f.svg)


podium-mcp agent session β€” one prompt opens Safari on a live iOS simulator, types github.com/hoainho, explores the profile and opens a repository

One prompt β†’ podium drives Safari live β†’ types the URL β†’ explores the profile β†’ opens a repo. Footage captured on a live iPhone 16 Pro simulator.


A podium is where a maestro stands β€” one place to conduct the whole orchestra. This MCP server unifies eight capability sets behind a single stdio endpoint:

  • Device & app management β€” iOS simulators (simctl), real iPhones (devicectl), and Android (adb) behind one platform-tagged device model.
  • Native UI inspection & gestures β€” route through idb/mobilecli with a Maestro fallback (no per-gesture JVM spin-up).
  • End-to-end flows & batch automation β€” declarative Maestro flows, ordered action batches, and an engineerβ†’QA flow exporter.
  • Trustworthy assertions β€” an oracle ladder (WebView-DOM β€Ί native a11y β€Ί Maestro) that returns falsifiable, evidenced verdicts and fails closed.
  • WebView DOM + network β€” resolve WKWebView DOM to tap coordinates, evaluate JS, drive navigation, and capture in-page HTTP traffic as JSON/HAR.
  • React Native debugging β€” Metro console logs, network requests, and in-app state over CDP, plus host/simulator crash reports.
  • Real devices β€” Android emulator/device via adb (gestures + uiautomator hierarchy); real iOS via devicectl lifecycle + an opt-in WebDriverAgent backend.
  • Canvas & game-engine automation, no vision β€” a canvas/WebGL brain drives Pixi/Konva/Fabric/Phaser/Three/Babylon UIs as addressable objects (validated live in WebKit). An experimental engine bridge drives Unity/GL via an AltTester-instrumented build (or a window.__podiumEngine WebGL bridge) β€” code-complete + mock-tested, not yet run against a live Unity build.

Rather than wiring several MCP servers into every client config, podium-mcp exposes everything behind one connection, with a shared execFile layer (no shell), consistent structured errors, automatic retry around Maestro's iOS-driver flakiness, and a single health-check tool to confirm what's available on the host.

Table of contents

Why

Driving a React Native app end-to-end usually means juggling several MCP servers β€” one for device/app control, one for UI flows, one for Metro/debugger logs, another for WebView inspection β€” each with its own config entry, quirks, and failure modes. podium-mcp collapses that into one server with:

  • a single execFile-based command runner (no shell β€” arguments are passed verbatim),
  • consistent structured errors (a tool never crashes the server),
  • automatic retry around Maestro's known iOS-driver flakiness,
  • graceful degradation when a toolchain (e.g. adb) is absent,
  • evidenced verdicts so an agent knows when a flow actually worked.

Benchmarks

Podium is built on two choices that make it fast and cheap: it drives UIs as structured data β€” never screenshots β€” and routes gestures through a native backend with no per-action JVM spin-up.

Token economics β€” no-vision is ~5Γ— cheaper

A screenshot-driven agent sends an image to a vision model on every step. Podium returns a compact structured element list instead. On an equivalent 8-step mobile flow (1179Γ—2556 screenshots vs ~20-element lists):

ApproachPer step8-step flow
Screenshot / vision loop~2,070 tokens16,557 tokens
Podium β€” no-vision, structured~390 tokens3,117 tokens
Savings5.3Γ—βˆ’13,440 tokens (βˆ’81%)
vision loop  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ  16,557 tokens
Podium       β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ  3,117 tokens   (5.3Γ— cheaper, βˆ’81%)

The gap compounds with every step β€” a 30-step session runs roughly 62k vs 12k input tokens. On top of per-step cost, the full 51-tool schema travels with every request (~3,612 tokens, ~71/tool); Podium keeps tool descriptions lean so the tool block never dominates the context window.

For canvas / WebGL UIs the advantage is structural, not just cheaper: the Canvas Brain addresses objects by name and text, where a screenshot-only agent must re-analyze pixels on every frame.

Speed β€” native-first gesture backend

Gestures route through idb / mobilecli instead of spinning up Maestro's JVM per action (measured on a live iPhone 16 Pro simulator):

OperationMaestro (per-call JVM)Podium nativeSpeedup
tap_on~14.7 s~0.6 s~24Γ—
inspect_screen~8.9 s~0.9 s~10Γ—

One connection, not six

All 51 tools β€” device & app control, UI automation, declarative Maestro flows, evidenced assertions, WebView DOM + network capture, React Native / Metro debugging, and no-vision canvas/WebGL automation (plus an experimental engine bridge for instrumented Unity/GL) β€” sit behind a single stdio endpoint, replacing the usual stack of half a dozen separate MCP servers.

Token figures are heuristic estimates (~4 chars/token; Anthropic's ~750 px/token image formula) β€” reproduce with npm run token-bench, or swap in the Anthropic count_tokens API for exact counts. Speed figures were measured on a live iPhone 16 Pro simulator (npm run benchmark).

The 51 tools

Every tool returns structured JSON and never throws β€” failures come back as MCP tool errors. See docs/tool-catalog.md for the authoritative per-parameter reference.

Platform support (v0.3.0): the gesture / inspect / lifecycle tools below run on iOS simulators, real iPhones (devicectl + opt-in WebDriverAgent via PODIUM_WDA_URL), and Android (emulator/device via adb; hierarchy from uiautomator). device_list tags each device with its platform and the backend is selected per target.

Game engine β€” Unity / GL via AltTester, no vision Β· experimental (4)

ToolKey paramsBacking engineBehavior
engine_inspectudid, by?, valueAltTester (TCP) / WebGL CDP bridgeLists engine objects (by name/path/component/text) with absolute screen coords β€” no screenshots
engine_tapudid, by?, valueAltTester / CDPResolves the object and taps its screen coordinates
engine_swipeudid, fromX/Y, toX/Y, durationMs?AltTester / CDPSwipe inside the engine view
engine_calludid, by?, value, component, method, parameters?AltTester / CDPInvokes a C# component method by reflection (the engine analog of a DOM event handler)

Status: experimental. The wire shapes are unit-tested against mocks; the AltTester path has not yet been validated against a live Unity build (engine-smoke skips until an instrumented build is provided), and Unity-WebGL needs the app to expose window.__podiumEngine. Engine tools require an AltTester-instrumented build (dev/staging) or that WebGL bridge; on a non-instrumented build they fail closed with an actionable error β€” never a vision fallback. For canvas/WebGL apps using a JS framework, the canvas brain below is the validated path.

Canvas brain β€” Pixi/Konva/Fabric/Phaser/Three/Babylon, no vision (3)

ToolKey paramsBacking engineBehavior
canvas_inspectudid, by?, value?, webviewId?injected scene-graph bridge (CDP eval)Lists canvas objects with tap-ready CSS-px coords β€” no screenshots
canvas_resolveudid, intent, webviewId?bridge + semantic resolverMaps a fuzzy intent ("close", "βœ•") to a ranked, evidenced target; fail-closed confidentEnough
canvas_tapudid, intent, bundleId?, webviewId?resolver + native tapResolves + taps the confident match at absolute screen coords (else fails closed)

Validated live: all six frameworks pass a Playwright-WebKit (β‰ˆ WKWebView) suite at DPR 1 + 3 (npm run test:canvas, 19 tests). Canvas tools require an inspectable WKWebView hosting a supported framework with its root reachable (commonly on window, or Pixi's __PIXI_APP__). No framework / no inspectable WebView β†’ fails closed with an actionable error β€” never a vision fallback.

Diagnostics (1)

ToolKey paramsBacking engineBehavior
podium_token_reportsteps?, screenshotWidth?, screenshotHeight?, elementsPerStep?, toolCount?token estimatorsNo-vision vs screenshot/vision-loop input tokens, the savings ratio, and the per-request tool-definition overhead

Health & toolchain (1)

ToolKey paramsBacking engineBehavior
podium_healthβ€”which probesNever fails; reports toolchain { xcrun, maestro, adb }, native backend, and platforms: [ios-sim, ios-real, android]

Device & simulator (6)

ToolKey paramsBacking engineBehavior
device_listβ€”simctl list -j + adb devicesMerged iOS inventory; adb absent β†’ android: { available: false } (detection-only)
device_bootudidsimctl bootIdempotent β€” already-booted β†’ alreadyBooted: true; waits up to 30 s
screen_sizeudidsimctl io screenshot + sips{ widthPx, heightPx } (real pixels)
orientation_getudidnative query β†’ screenshot heuristic{ orientation, basis } (exact when native)
set_locationudid, latitude, longitudesimctl location setCodifies the QA geo-spinner fix
open_urludid, urlsimctl openurlDeep links + https://

Apps (6)

ToolKey paramsBacking engineBehavior
app_installudid, path (.app/.zip)simctl installStructured tool error
app_launchudid, bundleIdsimctl launchExplicit 30 s timeout (cold RN launches no longer mis-report failure)
app_terminateudid, bundleIdsimctl terminateStructured tool error
app_uninstalludid, bundleIdsimctl uninstallStructured tool error
app_listudidsimctl listapps + plutil{ count, apps: [{ bundleId, name, type }] }
app_stateudid, bundleIdsimctl listapps + launchctl{ installed, running } β€” exact bundle-id match

Capture (3)

ToolKey paramsBacking engineBehavior
screenshotudid, saveTo?simctl io screenshotReturns path + byteSize (no base64 bloat)
record_startudid, saveTo? (.mp4)detached simctl io recordVideo{ ok, path, pid }; timestamped path + duration watchdog (PODIUM_MAX_RECORDING_MS); one per udid
record_stopudidSIGINT recorder + flush{ ok, path, sizeBytes }

UI inspection & gestures (8)

ToolKey paramsBacking engineBehavior
inspect_screenudid, compact?native flat AX list β†’ maestro hierarchycompact:true (default) returns only meaningful nodes
tap_onudid, bundleId, text|id|x+y, double?, long?native tap β†’ Maestro fallbacktext/id resolved via the element list; reports backend
input_textudid, bundleId, text, submit?native β†’ Maestro fallbackreports backend
swipeudid, bundleId, direction, start/end?native β†’ Maestro fallback%/pixel overrides resolved vs logical screen size
press_keyudid, bundleId, keynative β†’ Maestro fallbackback/power/tab are Android-only
orientation_setudid, bundleId, valuenative β†’ Maestro fallbackPORTRAIT / LANDSCAPE_LEFT / LANDSCAPE_RIGHT / UPSIDE_DOWN
tap_with_fallbackudid, x, y, maxRetries?, offsetStep?native tap + before/after oracleFor WebGL/Canvas overlays; no blind walk (offsetStep opt-in)
notification_bar_clearudid, bundleId?native tap + oracleDismisses the RN debug notification bar

Flows & batch automation (4)

ToolKey paramsBacking engineBehavior
run_stepsudid, bundleId, steps[]native backend (idb/mobilecli)Ordered action batch in one call; per-step results
run_flowudid + exactly one of yaml/files/dir(+tags), env?maestro testExactly-one-of validated before exec; per-step pass/fail
export_flowsteps[], output pathflow generatorExports a run_steps batch to a reusable Maestro flow (engineer→QA bridge)
cheat_sheetβ€”bundled assets/maestro-cheat-sheet.yamlFully offline Maestro syntax reference

Assertions & verdicts β€” the oracle ladder (5)

ToolKey paramsBacking engineBehavior
assert_visibleudid, text|id, …oracle ladder (WebView-DOM β€Ί a11y β€Ί Maestro)Evidenced pass/fail; reports which oracle proved it
assert_textudid, textoracle ladderby-text shorthand for assert_visible
assert_not_visibleudid, text|idoracle ladderFails closed β€” if absence can't be verified, it fails
wait_for_elementudid, text|id, timeoutMs?oracle ladder (polling)Polls until visible or times out
validate_flowudid, flow + assertionsoracle ladder + flow runTrustworthy, falsifiable verdict on whether a just-built flow works

WebView DOM & network (4)

ToolKey paramsBacking engineBehavior
webview_inspectudid, selector?, webviewId?, max?mobilecli (CDP)Resolves a CSS selector to DOM elements with absolute tapX/tapY
webview_evaludid, expression, webviewId?mobilecli (CDP)Runs JS in the page context; gated by PODIUM_DISABLE_WEBVIEW_EVAL=1
webview_navigateudid, action (goto/back/forward/reload), url?mobilecli (CDP)Drives WebView navigation
webview_networkudid, durationMs?, format (json/har)?, saveTo?, redact?, includeResources?CDP + in-page fetch/XHR shim + Resource TimingCaptures in-WebView HTTP traffic; exports redacted JSON or HAR 1.2

React Native debugging β€” Metro CDP (4)

ToolKey paramsBacking engineBehavior
metro_appsport? (8081)GET http://localhost:<port>/jsonDifferentiated errors (timeout vs not-running vs other)
metro_logswsUrl?/port?, durationMs?, maxLogs?WebSocket + CDP Runtime.enableAuto-discovers first app when URL omitted
metro_networkwsUrl?/port?, durationMs?, maxEntries?CDP Network.enableRequests (url/method/status/mimeType/ts)
metro_stateexpression?/wsUrl?/port?, timeoutMs?CDP Runtime.evaluateReads in-app state (default: globally-exposed Redux store)

Crash diagnostics (2)

ToolKey paramsBacking engineBehavior
crash_listprocessName?, sinceHours?, udid?host + sim DiagnosticReportsNewest-first; tagged source: host | simulator
crash_getid, udid?samePath-traversal-safe (basename only); truncates honestly

The oracle ladder β€” trustworthy assertions

"It works" is operationalized as a falsifiable, evidenced verdict β€” never "looks ok". Assertions and validate_flow resolve visibility through a three-rung ladder, using the strongest available signal:

  1. WebView DOM β€” when an inspectable WKWebView is present, query the real DOM.
  2. Native accessibility β€” the native AX element set (via idb/mobilecli).
  3. Maestro β€” assertVisible/assertNotVisible as the fallback.

assert_not_visible fails closed: if absence can't be positively verified (e.g. a WebView is unreadable), it reports failure rather than a false pass. Every verdict names the oracle that produced it, so an agent can weight its confidence.

Native-first gesture backend

Imperative gestures (tap_on, input_text, swipe, press_key, orientation_set, run_steps) and inspect_screen route through the fastest available backend, probed once and cached (with a short negative-cache TTL so a backend that starts after launch is picked up):

  1. idb β€” when both idb and idb_companion are installed (native, fastest).
  2. mobilecli β€” the bundled npm dependency (prebuilt Go binary). Default; no install.
  3. Maestro fallback β€” when no native backend resolves, or for actions it can't express (double/long-press, UPSIDE_DOWN). The gesture generates a minimal flow with launchApp: { stopApp: false }, foregrounding the app without restarting so state is preserved.

Each result reports the backend it used. Set PODIUM_DISABLE_NATIVE=1 to force Maestro. Eliminating the per-gesture JVM spin-up cut tap_on ~14.7 s β†’ ~0.6 s and inspect_screen ~8.9 s β†’ ~0.9 s on an iPhone 16 Pro simulator. Run npm run benchmark for a full pass/fail sweep.

Maestro flakiness retry: when the fallback runs, its iOS driver intermittently fails with Failed to connect to 127.0.0.1:<port>. Flows retry up to 2Γ— with 2 s / 5 s backoff and report the retries count; a persistent failure returns the raw output with remediation hints.

WebView & RN network introspection

Two distinct network layers, two tools:

  • metro_network captures requests on the RN/Hermes target via the CDP Network domain β€” the right tool for a native RN app's own fetch.
  • webview_network captures traffic inside a WKWebView: it injects a fetch/XHR recorder (rich β€” method/status/headers/body for calls after capture starts) and reads the browser's Performance Resource Timing buffer (includeResources, default on) β€” every request since navigation, including pre-capture ones (URL/timing/size). The merge yields a near-complete request list, exported as redacted JSON or HAR 1.2.

For an RN shell that hosts its UI in a WebView, the app's API calls run in the web layer β€” so metro_network sees nothing and webview_network is the tool to reach for. WebView tools require WKWebView.isInspectable = true (default in debug/staging builds; off in production); when none is found they return an actionable error.

Documented limits (by design, not bugs)

  • Canvas/WebGL needs a cooperating JS framework β€” the canvas brain automates Pixi/Konva/Fabric/Phaser/Three/Babylon UIs by selector when the app exposes its scene-graph root (validated live). A raw/custom WebGL canvas, an opaque/production build, or Unity without an AltTester / window.__podiumEngine bridge is not selector-addressable β€” fall back to tap_with_fallback with screenshot-derived coordinates, or instrument the build.
  • WebView tools are dev/QA only β€” production App Store builds typically set isInspectable = false; tools return an actionable error and fall back to coordinate taps.
  • WebView content-process memory is unreadable from the app sandbox (platform limit) β€” use indirect signals (memory warnings, process terminations).
  • Maestro text: matcher is full-string regex (IGNORE_CASE) β€” partial strings don't match; copy hierarchy text verbatim or anchor with .*.
  • Android requires adb on PATH β€” gestures / inspect / screenshot work once adb is present; when it's absent every Android path degrades to a structured "adb not found" result.
  • orientation_get is a screenshot-aspect heuristic when no native backend is present β€” iOS simulators expose no direct orientation query.
  • record_start/record_stop keep state in-process β€” serialize start β†’ … β†’ stop on one connection; one active recording per udid (a watchdog finalizes one that's never stopped).

Architecture

src/
  index.ts          # MCP server entry β€” registers every tool group, warms caches
  lib/
    exec.ts         # execFile-based runner (NO shell) + timeout/timedOut flag
    result.ts       # shared ok/error MCP content helpers
    simctl.ts       # xcrun simctl wrappers + device-list TTL cache
    native.ts       # gesture/inspect backend: idb β†’ mobilecli β†’ null (re-probe TTL)
    idb.ts          # idb gesture/inspect adapter
    gesture.ts      # unified native→Maestro executors (shared by screen + steps)
    oracle.ts       # the oracle ladder: WebView-DOM β€Ί a11y β€Ί Maestro
    maestro.ts      # Maestro engine: flow runner, idb retry, hierarchy
    export-maestro.ts # run_steps β†’ reusable Maestro flow
    har.ts          # HAR 1.2 export for webview_network
    webview.ts      # mobilecli CDP β€” WebView list/inspect/eval/navigate/network
    metro.ts        # Metro CDP β€” app discovery, logs, network, state
    crash.ts        # DiagnosticReports crash listing/reading
    recording.ts    # detached screen recording lifecycle + watchdog (platform-aware)
    device-target.ts # DeviceTarget model + PlatformDriver registry (v0.3.0)
    drivers/        # per-platform lifecycle: ios-sim, android, ios-real
    adb.ts          # Android adb driver (list/install/launch/screenshot/wm size)
    adb-backend.ts  # adb gesture/inspect (input + uiautomator β†’ AX elements)
    iosreal.ts      # real iOS via devicectl (list/install/launch) + capture
    wda.ts          # opt-in WebDriverAgent backend (/source + tap/swipe/keys)
    engine.ts       # no-vision engine client (AltTester + WebGL-in-WebView)
    engine-transport.ts # WebSocket transport for the AltTester bridge
    canvas-types.ts # Canvas Brain shared contract (CanvasObject, selectors)
    canvas-adapters.ts  # in-page bridge: detect + walk Pixi/Konva/Fabric/Phaser/Three/Babylon
    canvas-resolver.ts  # semantic "close brain": intent β†’ ranked, evidenced target
    canvas-a11y.ts  # Flutter/ARIA fallback reader β†’ CanvasObject (scaffolding, not wired β€” #9)
    canvas-vision.ts # opt-in vision fallback scaffolding (not wired β€” #9)
    token-report.ts # token estimators + no-vision vs vision-loop comparison
  tools/            # one file per group:
                    #   health, device, screen, steps, flow, assert, validate,
                    #   webview, debug, engine, canvas, token
assets/             # bundled offline Maestro cheat sheet + demo.gif
scripts/            # benchmark.ts, compare-mcps.ts, token-bench.mjs
e2e/                # smoke suites (smoke / full-smoke / webview-network-live / android-smoke / engine-smoke)
test/canvas-e2e/    # live Playwright-WebKit canvas bridge suite (6 frameworks)
docs/               # tool catalog, e2e transcript, roadmap, token-economics

Development & testing

npm run build       # tsc
npm run typecheck   # tsc --noEmit
npm test            # vitest run β€” 359 unit/integration tests (exec/network mocked, no sim needed)
npm run test:canvas # live canvas bridge suite in Playwright WebKit β€” 19 tests (run `npx playwright install webkit` first)
npm run benchmark   # spawn a fresh server over stdio and sweep the tool suite
node e2e/smoke.e2e.mjs        # real E2E against a booted simulator (macOS + Xcode)
node e2e/full-smoke.e2e.mjs   # drives the iOS-sim tool handlers (happy + structured-error paths)
node e2e/android-smoke.e2e.mjs # Android emulator/device smoke (story A3)
node e2e/engine-smoke.e2e.mjs  # AltTester engine smoke; skips without an instrumented build (story C4)

359 unit/integration tests across 31 files, plus 19 live canvas-bridge tests (378 total), all passing β€” including the v0.3.0 device-target registry, the Android adb driver + uiautomator parser, the AltTester engine client + WebGL bridge, the devicectl/WDA real-iOS parsers, plus the v0.2.0 oracle ladder, recording watchdog, gesture-parity, HAR export, WebView, and Metro paths.

Standards: TypeScript strict, no as any / @ts-ignore, no shell execution (all commands via lib/exec.ts), tools return structured errors instead of throwing. See CONTRIBUTING.md for the "add a new tool" checklist.

E2E on CI: the E2E (simulator) workflow boots a real iOS simulator on a macOS runner and runs the smoke suites nightly + on demand (not a PR gate β€” simulator runs are slow). full-smoke.e2e.mjs asserts the happy path where a target exists and the real structured-error path where a dependency is absent (a debug isInspectable app for WebView; a connected RN app for metro_*).

Roadmap & contributing

podium-mcp is production-ready for iOS/Android UI automation and no-vision canvas/WebGL (Pixi/Konva/Fabric/Phaser/Three/Babylon β€” validated live). The frontier, where a contributor can make a real dent, lives in open issues:

High-impact β€” help wanted

  • #1 β€” validate the AltTester/Unity engine path against a live instrumented Unity build (the biggest gap to real Unity automation).
  • #2 β€” real-device WKWebView e2e for the canvas brain (today validated in Playwright WebKit).
  • #3 β€” Unity-WebGL adapter: auto-detect + a drop-in window.__podiumEngine bridge.

Good first issues β€” good first issue

  • #4 β€” more canvas adapters (PlayCanvas, Cocos Creator, p5.js).
  • #5 β€” expose canvas_hittest / canvas_object_rect tools.
  • #7 β€” exact token counts via the Anthropic count_tokens API.
  • #6 β€” address Konva Group/Container targets.

Adding a tool follows one checklist in CONTRIBUTING.md: TypeScript strict, no shell, structured-errors-never-throw, a vitest test, and a row in the tool catalog. PRs welcome.

Releasing

server.json is the official MCP Registry manifest. Pushing a v* tag runs Publish to npm then Publish to MCP Registry (GitHub OIDC for the io.github.hoainho/* namespace β€” no long-lived token). Both workflows run typecheck β†’ build β†’ test as a gate first; the registry publish only succeeds once the matching npm version is live, and versions are immutable.

Prompt playbook & references

  • prompts/ β€” copy-paste prompts for e2e flows, test cases, feature verification, bug fixing, and device control. Each names the podium tools it drives and was validated on a real simulator. Start with prompts/README.md.
  • docs/tool-catalog.md β€” authoritative tool-by-tool reference.
  • docs/e2e-demo.md β€” a real transcript against a booted iPhone 16 Pro simulator running a production RN app.

Design ideas

  • One podium, one connection. A single server fronts every mobile capability so an agent configures one endpoint and discovers all 51 tools at once.
  • Safe by construction. Every external command runs through an execFile layer with an explicit argument array β€” never a shell string.
  • Never crash the conductor. Tools return structured results and errors instead of throwing; one bad call can't take the server down.
  • Degrade, don't fail. A missing toolchain (e.g. Android's adb) yields an informative result rather than a hard error.
  • Prove it, don't guess. Assertions return evidenced verdicts via the oracle ladder and fail closed when they can't verify.

Security

Please report vulnerabilities privately per SECURITY.md β€” do not open a public issue. SECURITY.md also documents the webview_eval / run_flow trust boundary and the PII-in-transcript caveat.