{
  "markdown": "<div align=\"center\">\n\n# podium-mcp\n\n**One baton. Every instrument.**\n\nA 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.\n\n[![npm](https://img.shields.io/npm/v/podium-mcp?logo=npm&color=CB3837)](https://www.npmjs.com/package/podium-mcp)\n[![Glama](https://glama.ai/mcp/servers/hoainho/podium-mcp/badge)](https://glama.ai/mcp/servers/hoainho/podium-mcp)\n[![mcp.so](https://img.shields.io/badge/mcp.so-listed-7C3AED)](https://mcp.so/server/io.github.hoainho/podium-mcp)\n[![CI](https://github.com/hoainho/podium-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/hoainho/podium-mcp/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n\n[![tools 51](https://img.shields.io/badge/tools-51-7C3AED.svg)](#the-51-tools)\n[![tests 378](https://img.shields.io/badge/tests-378%20passing-brightgreen.svg)](#development--testing)\n[![tokens ~5x cheaper](https://img.shields.io/badge/tokens-~5x%20cheaper%20(no--vision)-2ea44f.svg)](#benchmarks)\n[![canvas 6 live engines](https://img.shields.io/badge/canvas-6%20live%20engines-2ea44f.svg)](#the-51-tools)\n[![Node ≥22](https://img.shields.io/badge/node-%E2%89%A522-339933?logo=node.js&logoColor=white)](package.json)\n[![TypeScript strict](https://img.shields.io/badge/TypeScript-strict-3178C6?logo=typescript&logoColor=white)](tsconfig.json)\n[![MCP stdio](https://img.shields.io/badge/MCP-stdio-7C3AED)](https://modelcontextprotocol.io)\n[![PRs welcome](https://img.shields.io/badge/PRs-welcome-2ea44f.svg)](#roadmap--contributing)\n\n<br/>\n\n<img src=\"assets/demo.gif\" alt=\"podium-mcp agent session — one prompt opens Safari on a live iOS simulator, types github.com/hoainho, explores the profile and opens a repository\" width=\"300\" />\n\n<sub><i>One prompt → podium drives Safari live → types the URL → explores the profile → opens a repo. Footage captured on a live iPhone 16 Pro simulator.</i></sub>\n\n</div>\n\n---\n\nA 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:\n\n- **Device & app management** — iOS simulators (`simctl`), real iPhones (`devicectl`), and Android (`adb`) behind one platform-tagged device model.\n- **Native UI inspection & gestures** — route through `idb`/`mobilecli` with a Maestro fallback (no per-gesture JVM spin-up).\n- **End-to-end flows & batch automation** — declarative Maestro flows, ordered action batches, and an engineer→QA flow exporter.\n- **Trustworthy assertions** — an *oracle ladder* (WebView-DOM › native a11y › Maestro) that returns falsifiable, evidenced verdicts and **fails closed**.\n- **WebView DOM + network** — resolve `WKWebView` DOM to tap coordinates, evaluate JS, drive navigation, and capture in-page HTTP traffic as JSON/HAR.\n- **React Native debugging** — Metro console logs, network requests, and in-app state over CDP, plus host/simulator crash reports.\n- **Real devices** — Android emulator/device via `adb` (gestures + `uiautomator` hierarchy); real iOS via `devicectl` lifecycle + an opt-in WebDriverAgent backend.\n- **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.\n\nRather 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.\n\n## Table of contents\n\n- [Why](#why)\n- [Benchmarks](#benchmarks)\n- [Requirements](#requirements)\n- [Install](#install)\n- [Usage](#usage)\n- [Quick start](#quick-start-order-of-use)\n- [The 51 tools](#the-51-tools)\n- [The oracle ladder — trustworthy assertions](#the-oracle-ladder--trustworthy-assertions)\n- [Native-first gesture backend](#native-first-gesture-backend)\n- [WebView & RN network introspection](#webview--rn-network-introspection)\n- [Documented limits](#documented-limits-by-design-not-bugs)\n- [Architecture](#architecture)\n- [Development & testing](#development--testing)\n- [Roadmap & contributing](#roadmap--contributing)\n- [Releasing](#releasing)\n- [Prompt playbook & references](#prompt-playbook--references)\n- [Design ideas](#design-ideas)\n- [Contributing](#contributing) · [Security](#security) · [License](#license)\n\n## Why\n\nDriving a React Native app end-to-end usually means juggling several MCP servers —\none for device/app control, one for UI flows, one for Metro/debugger logs, another\nfor WebView inspection — each with its own config entry, quirks, and failure modes.\npodium-mcp collapses that into **one** server with:\n\n- a single `execFile`-based command runner (no shell — arguments are passed verbatim),\n- consistent structured errors (a tool never crashes the server),\n- automatic retry around Maestro's known iOS-driver flakiness,\n- graceful degradation when a toolchain (e.g. `adb`) is absent,\n- **evidenced verdicts** so an agent knows when a flow *actually* worked.\n\n## Benchmarks\n\nPodium is built on two choices that make it **fast** and **cheap**: it drives UIs\nas *structured data* — never screenshots — and routes gestures through a *native\nbackend* with no per-action JVM spin-up.\n\n### Token economics — no-vision is ~5× cheaper\n\nA screenshot-driven agent sends an image to a vision model on **every step**.\nPodium returns a compact structured element list instead. On an equivalent 8-step\nmobile flow (1179×2556 screenshots vs ~20-element lists):\n\n| Approach | Per step | 8-step flow |\n| --- | ---: | ---: |\n| Screenshot / vision loop | ~2,070 tokens | **16,557 tokens** |\n| Podium — no-vision, structured | ~390 tokens | **3,117 tokens** |\n| **Savings** | **5.3×** | **−13,440 tokens (−81%)** |\n\n```\nvision loop  ████████████████████████████████  16,557 tokens\nPodium       ██████  3,117 tokens   (5.3× cheaper, −81%)\n```\n\nThe gap **compounds with every step** — a 30-step session runs roughly **62k vs\n12k** input tokens. On top of per-step cost, the full **51-tool schema travels\nwith every request (~3,612 tokens, ~71/tool)**; Podium keeps tool descriptions\nlean so the tool block never dominates the context window.\n\nFor canvas / WebGL UIs the advantage is **structural**, not just cheaper:\nthe [Canvas Brain](#the-51-tools) addresses objects by name and text, where a\nscreenshot-only agent must re-analyze pixels on every frame.\n\n### Speed — native-first gesture backend\n\nGestures route through `idb` / `mobilecli` instead of spinning up Maestro's JVM\nper action (measured on a live iPhone 16 Pro simulator):\n\n| Operation | Maestro (per-call JVM) | Podium native | Speedup |\n| --- | ---: | ---: | ---: |\n| `tap_on` | ~14.7 s | **~0.6 s** | **~24×** |\n| `inspect_screen` | ~8.9 s | **~0.9 s** | **~10×** |\n\n### One connection, not six\n\nAll **51 tools** — device & app control, UI automation, declarative Maestro flows,\nevidenced assertions, WebView DOM + network capture, React Native / Metro\ndebugging, and no-vision canvas/WebGL automation (plus an experimental engine bridge for instrumented Unity/GL) — sit behind a **single\nstdio endpoint**, replacing the usual stack of half a dozen separate MCP servers.\n\n> Token figures are heuristic estimates (~4 chars/token; Anthropic's ~750 px/token\n> image formula) — reproduce with `npm run token-bench`, or swap in the Anthropic\n> `count_tokens` API for exact counts. Speed figures were measured on a live\n> iPhone 16 Pro simulator (`npm run benchmark`).\n\n## Requirements\n\n- **macOS** with Xcode command-line tools (`xcrun`, `simctl`)\n- **Node.js ≥ 22** (uses native `fetch` and `WebSocket`; `.npmrc` sets `engine-strict=true`)\n- **`mobilecli`** — bundled automatically as an npm dependency; the default native gesture + WebView backend (no separate install)\n- *(optional)* **[`idb`](https://fbidb.io)** (`idb` + `idb_companion`) — preferred native gesture backend when both are present; auto-detected\n- *(optional)* **[Maestro](https://maestro.mobile.dev)** on `PATH` (or at `~/.maestro/bin`) — the `run_flow` engine and the gesture fallback path\n- *(optional)* a running **Metro** bundler for the `metro_*` debugging tools\n- *(optional)* Android SDK + `adb` — adb paths are **detection-only** and degrade gracefully when absent\n\n> **Platform scope (v0.3.0):** podium automates **iOS simulators**, **real iPhones** (`devicectl` lifecycle + opt-in WebDriverAgent), and **Android** emulators/devices (`adb` gestures + `uiautomator` hierarchy). `device_list` tags each target with its platform and the backend is selected per target. When a toolchain (e.g. `adb`) is absent, those paths degrade to an informative result instead of failing.\n\n## Install\n\n### Claude Code plugin (recommended)\n\nNo manual config — one-time marketplace setup, then install:\n\n```\n/plugin marketplace add github:hoainho/podium-mcp\n/plugin install podium-mcp@podium\n```\n\nThe plugin auto-starts the MCP server (all 51 tools) and ships five skills:\n\n| Skill | Invoke | What it does |\n|---|---|---|\n| Device info | `/podium-mcp:device-info <UDID> [<BUNDLE_ID>]` | Health check, screen size, orientation, app list |\n| E2E flow | `/podium-mcp:e2e <UDID> <BUNDLE_ID> [path or description]` | Run or author a Maestro flow |\n| Bug repro | `/podium-mcp:bug-repro <UDID> <BUNDLE_ID> <description>` | Video + logs + crash evidence capture |\n| RN debug | `/podium-mcp:rn-debug [UDID] [logs\\|apps\\|crash\\|all]` | Metro logs, connected apps, crash reports |\n| Canvas brain | `/podium-mcp:canvas <UDID> <intent>` | Inspect / resolve / tap canvas-WebGL UIs, no vision |\n\n### npx (zero install)\n\n```json\n{\n  \"mcpServers\": {\n    \"podium\": { \"command\": \"npx\", \"args\": [\"-y\", \"podium-mcp\"] }\n  }\n}\n```\n\n### Manual (from source)\n\n```bash\ngit clone git@github.com:hoainho/podium-mcp.git\ncd podium-mcp\nnpm install\nnpm run build\n```\n\n## Usage\n\nRegister the built server with any MCP client. **Claude Code** (`.mcp.json`):\n\n```json\n{\n  \"mcpServers\": {\n    \"podium\": {\n      \"type\": \"stdio\",\n      \"command\": \"node\",\n      \"args\": [\"/absolute/path/to/podium-mcp/dist/index.js\"]\n    }\n  }\n}\n```\n\nQuick manual smoke test over raw stdio (lists the 51 registered tools):\n\n```bash\nprintf '%s\\n' \\\n  '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{},\"clientInfo\":{\"name\":\"smoke\",\"version\":\"0\"}}}' \\\n  '{\"jsonrpc\":\"2.0\",\"method\":\"notifications/initialized\"}' \\\n  '{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/list\"}' | node dist/index.js\n```\n\nAlways call **`podium_health`** first to confirm which toolchain is available on the host.\n\n## Quick start (order of use)\n\n1. **`podium_health`** — confirm `xcrun` / `maestro` / native backend availability.\n2. **`device_list`** — pick a booted simulator `udid`.\n3. **Read state** — `app_list`, `app_state`, `screen_size`, `orientation_get`.\n4. **Drive the device** — `app_launch`, then `tap_on` / `input_text` / `swipe` / `press_key`, plus `set_location` and `orientation_set`. Batch several with `run_steps`.\n5. **Author & verify** — `inspect_screen` to discover elements, `run_flow` for declarative checks, then `assert_visible` / `validate_flow` for an **evidenced** verdict.\n6. **Inspect WebViews** — `webview_inspect` → tap coordinates, `webview_eval`, `webview_navigate`, `webview_network`.\n7. **Capture & debug** — `screenshot` / `record_start`→`record_stop`; `metro_logs` / `metro_network` / `metro_state`; `crash_list` / `crash_get`.\n\n## The 51 tools\n\n> Every tool returns structured JSON and never throws — failures come back as MCP tool errors. See [`docs/tool-catalog.md`](docs/tool-catalog.md) for the authoritative per-parameter reference.\n>\n> **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.\n\n### Game engine — Unity / GL via AltTester, no vision · experimental (4)\n\n| Tool | Key params | Backing engine | Behavior |\n|---|---|---|---|\n| `engine_inspect` | udid, by?, value | AltTester (TCP) / WebGL CDP bridge | Lists engine objects (by name/path/component/text) with absolute screen coords — **no screenshots** |\n| `engine_tap` | udid, by?, value | AltTester / CDP | Resolves the object and taps its screen coordinates |\n| `engine_swipe` | udid, fromX/Y, toX/Y, durationMs? | AltTester / CDP | Swipe inside the engine view |\n| `engine_call` | udid, by?, value, component, method, parameters? | AltTester / CDP | Invokes a C# component method by reflection (the engine analog of a DOM event handler) |\n\n> **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**.\n\n### Canvas brain — Pixi/Konva/Fabric/Phaser/Three/Babylon, no vision (3)\n\n| Tool | Key params | Backing engine | Behavior |\n|---|---|---|---|\n| `canvas_inspect` | udid, by?, value?, webviewId? | injected scene-graph bridge (CDP eval) | Lists canvas objects with tap-ready CSS-px coords — **no screenshots** |\n| `canvas_resolve` | udid, intent, webviewId? | bridge + semantic resolver | Maps a fuzzy intent (\"close\", \"✕\") to a ranked, **evidenced** target; fail-closed `confidentEnough` |\n| `canvas_tap` | udid, intent, bundleId?, webviewId? | resolver + native tap | Resolves + taps the confident match at absolute screen coords (else fails closed) |\n\n> **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.\n\n### Diagnostics (1)\n\n| Tool | Key params | Backing engine | Behavior |\n|---|---|---|---|\n| `podium_token_report` | steps?, screenshotWidth?, screenshotHeight?, elementsPerStep?, toolCount? | token estimators | No-vision vs screenshot/vision-loop input tokens, the savings ratio, and the per-request tool-definition overhead |\n\n### Health & toolchain (1)\n\n| Tool | Key params | Backing engine | Behavior |\n|---|---|---|---|\n| `podium_health` | — | `which` probes | Never fails; reports `toolchain { xcrun, maestro, adb }`, native backend, and `platforms: [ios-sim, ios-real, android]` |\n\n### Device & simulator (6)\n\n| Tool | Key params | Backing engine | Behavior |\n|---|---|---|---|\n| `device_list` | — | `simctl list -j` + `adb devices` | Merged iOS inventory; adb absent → `android: { available: false }` (detection-only) |\n| `device_boot` | udid | `simctl boot` | Idempotent — already-booted → `alreadyBooted: true`; waits up to 30 s |\n| `screen_size` | udid | `simctl io screenshot` + `sips` | `{ widthPx, heightPx }` (real pixels) |\n| `orientation_get` | udid | native query → screenshot heuristic | `{ orientation, basis }` (exact when native) |\n| `set_location` | udid, latitude, longitude | `simctl location set` | Codifies the QA geo-spinner fix |\n| `open_url` | udid, url | `simctl openurl` | Deep links + `https://` |\n\n### Apps (6)\n\n| Tool | Key params | Backing engine | Behavior |\n|---|---|---|---|\n| `app_install` | udid, path (.app/.zip) | `simctl install` | Structured tool error |\n| `app_launch` | udid, bundleId | `simctl launch` | Explicit 30 s timeout (cold RN launches no longer mis-report failure) |\n| `app_terminate` | udid, bundleId | `simctl terminate` | Structured tool error |\n| `app_uninstall` | udid, bundleId | `simctl uninstall` | Structured tool error |\n| `app_list` | udid | `simctl listapps` + `plutil` | `{ count, apps: [{ bundleId, name, type }] }` |\n| `app_state` | udid, bundleId | `simctl listapps` + `launchctl` | `{ installed, running }` — **exact** bundle-id match |\n\n### Capture (3)\n\n| Tool | Key params | Backing engine | Behavior |\n|---|---|---|---|\n| `screenshot` | udid, saveTo? | `simctl io screenshot` | Returns `path` + `byteSize` (no base64 bloat) |\n| `record_start` | udid, saveTo? (.mp4) | detached `simctl io recordVideo` | `{ ok, path, pid }`; timestamped path + duration watchdog (`PODIUM_MAX_RECORDING_MS`); one per udid |\n| `record_stop` | udid | SIGINT recorder + flush | `{ ok, path, sizeBytes }` |\n\n### UI inspection & gestures (8)\n\n| Tool | Key params | Backing engine | Behavior |\n|---|---|---|---|\n| `inspect_screen` | udid, compact? | native flat AX list → `maestro hierarchy` | `compact:true` (default) returns only meaningful nodes |\n| `tap_on` | udid, bundleId, text\\|id\\|x+y, double?, long? | native tap → Maestro fallback | text/id resolved via the element list; reports `backend` |\n| `input_text` | udid, bundleId, text, submit? | native → Maestro fallback | reports `backend` |\n| `swipe` | udid, bundleId, direction, start/end? | native → Maestro fallback | %/pixel overrides resolved vs logical screen size |\n| `press_key` | udid, bundleId, key | native → Maestro fallback | back/power/tab are Android-only |\n| `orientation_set` | udid, bundleId, value | native → Maestro fallback | PORTRAIT / LANDSCAPE_LEFT / LANDSCAPE_RIGHT / UPSIDE_DOWN |\n| `tap_with_fallback` | udid, x, y, maxRetries?, offsetStep? | native tap + before/after oracle | For WebGL/Canvas overlays; **no blind walk** (`offsetStep` opt-in) |\n| `notification_bar_clear` | udid, bundleId? | native tap + oracle | Dismisses the RN debug notification bar |\n\n### Flows & batch automation (4)\n\n| Tool | Key params | Backing engine | Behavior |\n|---|---|---|---|\n| `run_steps` | udid, bundleId, steps[] | native backend (idb/mobilecli) | Ordered action batch in **one call**; per-step results |\n| `run_flow` | udid + exactly one of yaml/files/dir(+tags), env? | `maestro test` | Exactly-one-of validated before exec; per-step pass/fail |\n| `export_flow` | steps[], output path | flow generator | Exports a `run_steps` batch to a reusable Maestro flow (engineer→QA bridge) |\n| `cheat_sheet` | — | bundled `assets/maestro-cheat-sheet.yaml` | Fully offline Maestro syntax reference |\n\n### Assertions & verdicts — the oracle ladder (5)\n\n| Tool | Key params | Backing engine | Behavior |\n|---|---|---|---|\n| `assert_visible` | udid, text\\|id, … | oracle ladder (WebView-DOM › a11y › Maestro) | Evidenced pass/fail; reports which oracle proved it |\n| `assert_text` | udid, text | oracle ladder | by-text shorthand for `assert_visible` |\n| `assert_not_visible` | udid, text\\|id | oracle ladder | **Fails closed** — if absence can't be verified, it fails |\n| `wait_for_element` | udid, text\\|id, timeoutMs? | oracle ladder (polling) | Polls until visible or times out |\n| `validate_flow` | udid, flow + assertions | oracle ladder + flow run | Trustworthy, falsifiable verdict on whether a just-built flow works |\n\n### WebView DOM & network (4)\n\n| Tool | Key params | Backing engine | Behavior |\n|---|---|---|---|\n| `webview_inspect` | udid, selector?, webviewId?, max? | `mobilecli` (CDP) | Resolves a CSS selector to DOM elements with absolute `tapX`/`tapY` |\n| `webview_eval` | udid, expression, webviewId? | `mobilecli` (CDP) | Runs JS in the page context; gated by `PODIUM_DISABLE_WEBVIEW_EVAL=1` |\n| `webview_navigate` | udid, action (goto/back/forward/reload), url? | `mobilecli` (CDP) | Drives WebView navigation |\n| `webview_network` | udid, durationMs?, format (json/har)?, saveTo?, redact?, includeResources? | CDP + in-page fetch/XHR shim + Resource Timing | Captures in-WebView HTTP traffic; exports **redacted JSON or HAR 1.2** |\n\n### React Native debugging — Metro CDP (4)\n\n| Tool | Key params | Backing engine | Behavior |\n|---|---|---|---|\n| `metro_apps` | port? (8081) | GET `http://localhost:<port>/json` | Differentiated errors (timeout vs not-running vs other) |\n| `metro_logs` | wsUrl?/port?, durationMs?, maxLogs? | WebSocket + CDP `Runtime.enable` | Auto-discovers first app when URL omitted |\n| `metro_network` | wsUrl?/port?, durationMs?, maxEntries? | CDP `Network.enable` | Requests (url/method/status/mimeType/ts) |\n| `metro_state` | expression?/wsUrl?/port?, timeoutMs? | CDP `Runtime.evaluate` | Reads in-app state (default: globally-exposed Redux store) |\n\n### Crash diagnostics (2)\n\n| Tool | Key params | Backing engine | Behavior |\n|---|---|---|---|\n| `crash_list` | processName?, sinceHours?, udid? | host + sim `DiagnosticReports` | Newest-first; tagged `source: host \\| simulator` |\n| `crash_get` | id, udid? | same | Path-traversal-safe (basename only); truncates honestly |\n\n## The oracle ladder — trustworthy assertions\n\n\"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:\n\n1. **WebView DOM** — when an inspectable `WKWebView` is present, query the real DOM.\n2. **Native accessibility** — the native AX element set (via `idb`/`mobilecli`).\n3. **Maestro** — `assertVisible`/`assertNotVisible` as the fallback.\n\n`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.\n\n## Native-first gesture backend\n\nImperative 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):\n\n1. **`idb`** — when both `idb` and `idb_companion` are installed (native, fastest).\n2. **`mobilecli`** — the bundled npm dependency (prebuilt Go binary). Default; no install.\n3. **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.\n\nEach 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.\n\n**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.\n\n## WebView & RN network introspection\n\nTwo distinct network layers, two tools:\n\n- **`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`.\n- **`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**.\n\nFor 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.\n\n## Documented limits (by design, not bugs)\n\n- **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.\n- **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.\n- **WebView content-process memory is unreadable** from the app sandbox (platform limit) — use indirect signals (memory warnings, process terminations).\n- **Maestro `text:` matcher is full-string regex (IGNORE_CASE)** — partial strings don't match; copy hierarchy `text` verbatim or anchor with `.*`.\n- **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.\n- **`orientation_get` is a screenshot-aspect heuristic** when no native backend is present — iOS simulators expose no direct orientation query.\n- **`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).\n\n## Architecture\n\n```\nsrc/\n  index.ts          # MCP server entry — registers every tool group, warms caches\n  lib/\n    exec.ts         # execFile-based runner (NO shell) + timeout/timedOut flag\n    result.ts       # shared ok/error MCP content helpers\n    simctl.ts       # xcrun simctl wrappers + device-list TTL cache\n    native.ts       # gesture/inspect backend: idb → mobilecli → null (re-probe TTL)\n    idb.ts          # idb gesture/inspect adapter\n    gesture.ts      # unified native→Maestro executors (shared by screen + steps)\n    oracle.ts       # the oracle ladder: WebView-DOM › a11y › Maestro\n    maestro.ts      # Maestro engine: flow runner, idb retry, hierarchy\n    export-maestro.ts # run_steps → reusable Maestro flow\n    har.ts          # HAR 1.2 export for webview_network\n    webview.ts      # mobilecli CDP — WebView list/inspect/eval/navigate/network\n    metro.ts        # Metro CDP — app discovery, logs, network, state\n    crash.ts        # DiagnosticReports crash listing/reading\n    recording.ts    # detached screen recording lifecycle + watchdog (platform-aware)\n    device-target.ts # DeviceTarget model + PlatformDriver registry (v0.3.0)\n    drivers/        # per-platform lifecycle: ios-sim, android, ios-real\n    adb.ts          # Android adb driver (list/install/launch/screenshot/wm size)\n    adb-backend.ts  # adb gesture/inspect (input + uiautomator → AX elements)\n    iosreal.ts      # real iOS via devicectl (list/install/launch) + capture\n    wda.ts          # opt-in WebDriverAgent backend (/source + tap/swipe/keys)\n    engine.ts       # no-vision engine client (AltTester + WebGL-in-WebView)\n    engine-transport.ts # WebSocket transport for the AltTester bridge\n    canvas-types.ts # Canvas Brain shared contract (CanvasObject, selectors)\n    canvas-adapters.ts  # in-page bridge: detect + walk Pixi/Konva/Fabric/Phaser/Three/Babylon\n    canvas-resolver.ts  # semantic \"close brain\": intent → ranked, evidenced target\n    canvas-a11y.ts  # Flutter/ARIA fallback reader → CanvasObject (scaffolding, not wired — #9)\n    canvas-vision.ts # opt-in vision fallback scaffolding (not wired — #9)\n    token-report.ts # token estimators + no-vision vs vision-loop comparison\n  tools/            # one file per group:\n                    #   health, device, screen, steps, flow, assert, validate,\n                    #   webview, debug, engine, canvas, token\nassets/             # bundled offline Maestro cheat sheet + demo.gif\nscripts/            # benchmark.ts, compare-mcps.ts, token-bench.mjs\ne2e/                # smoke suites (smoke / full-smoke / webview-network-live / android-smoke / engine-smoke)\ntest/canvas-e2e/    # live Playwright-WebKit canvas bridge suite (6 frameworks)\ndocs/               # tool catalog, e2e transcript, roadmap, token-economics\n```\n\n## Development & testing\n\n```bash\nnpm run build       # tsc\nnpm run typecheck   # tsc --noEmit\nnpm test            # vitest run — 359 unit/integration tests (exec/network mocked, no sim needed)\nnpm run test:canvas # live canvas bridge suite in Playwright WebKit — 19 tests (run `npx playwright install webkit` first)\nnpm run benchmark   # spawn a fresh server over stdio and sweep the tool suite\nnode e2e/smoke.e2e.mjs        # real E2E against a booted simulator (macOS + Xcode)\nnode e2e/full-smoke.e2e.mjs   # drives the iOS-sim tool handlers (happy + structured-error paths)\nnode e2e/android-smoke.e2e.mjs # Android emulator/device smoke (story A3)\nnode e2e/engine-smoke.e2e.mjs  # AltTester engine smoke; skips without an instrumented build (story C4)\n```\n\n**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.\n\nStandards: 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](CONTRIBUTING.md) for the \"add a new tool\" checklist.\n\n**E2E on CI:** the [`E2E (simulator)`](.github/workflows/e2e-sim.yml) 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_*`).\n\n## Roadmap & contributing\n\npodium-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:\n\n**High-impact** — [`help wanted`](https://github.com/hoainho/podium-mcp/labels/help%20wanted)\n- [#1](https://github.com/hoainho/podium-mcp/issues/1) — validate the AltTester/Unity engine path against a **live instrumented Unity build** (the biggest gap to real Unity automation).\n- [#2](https://github.com/hoainho/podium-mcp/issues/2) — **real-device WKWebView e2e** for the canvas brain (today validated in Playwright WebKit).\n- [#3](https://github.com/hoainho/podium-mcp/issues/3) — **Unity-WebGL adapter**: auto-detect + a drop-in `window.__podiumEngine` bridge.\n\n**Good first issues** — [`good first issue`](https://github.com/hoainho/podium-mcp/labels/good%20first%20issue)\n- [#4](https://github.com/hoainho/podium-mcp/issues/4) — more canvas adapters (PlayCanvas, Cocos Creator, p5.js).\n- [#5](https://github.com/hoainho/podium-mcp/issues/5) — expose `canvas_hittest` / `canvas_object_rect` tools.\n- [#7](https://github.com/hoainho/podium-mcp/issues/7) — exact token counts via the Anthropic `count_tokens` API.\n- [#6](https://github.com/hoainho/podium-mcp/issues/6) — address Konva Group/Container targets.\n\nAdding a tool follows one checklist in [CONTRIBUTING.md](CONTRIBUTING.md): TypeScript strict, no shell, structured-errors-never-throw, a vitest test, and a row in the [tool catalog](docs/tool-catalog.md). PRs welcome.\n\n## Releasing\n\n`server.json` is the official MCP Registry manifest. Pushing a `v*` tag runs\n[`Publish to npm`](.github/workflows/publish-npm.yml) then\n[`Publish to MCP Registry`](.github/workflows/publish-mcp-registry.yml) (GitHub OIDC for the\n`io.github.hoainho/*` namespace — no long-lived token). Both workflows run `typecheck → build → test`\nas a gate first; the registry publish only succeeds once the matching npm version is live, and\nversions are immutable.\n\n## Prompt playbook & references\n\n- **[`prompts/`](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`](prompts/README.md).\n- **[`docs/tool-catalog.md`](docs/tool-catalog.md)** — authoritative tool-by-tool reference.\n- **[`docs/e2e-demo.md`](docs/e2e-demo.md)** — a real transcript against a booted iPhone 16 Pro simulator running a production RN app.\n\n## Design ideas\n\n- **One podium, one connection.** A single server fronts every mobile capability so an agent configures one endpoint and discovers all 51 tools at once.\n- **Safe by construction.** Every external command runs through an `execFile` layer with an explicit argument array — never a shell string.\n- **Never crash the conductor.** Tools return structured results and errors instead of throwing; one bad call can't take the server down.\n- **Degrade, don't fail.** A missing toolchain (e.g. Android's `adb`) yields an informative result rather than a hard error.\n- **Prove it, don't guess.** Assertions return evidenced verdicts via the oracle ladder and fail closed when they can't verify.\n\n## Contributing\n\nContributions welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) and the\n[Code of Conduct](CODE_OF_CONDUCT.md). Use the issue templates for bugs and feature requests.\n\n## Security\n\nPlease report vulnerabilities privately per [SECURITY.md](SECURITY.md) — do not open a public issue.\nSECURITY.md also documents the `webview_eval` / `run_flow` trust boundary and the PII-in-transcript caveat.\n\n## License\n\n[MIT](LICENSE) © 2026 hoainho\n",
  "bytes": 34502,
  "sha": "ccbfb496d0d98378dcf4ce10ed0211d5dc4d01cd4a1594a8513a9d07eb57ecdb",
  "repo_slug": "hoainho/podium-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_hoainho_podium_mcp_59589210/readme"
}