# ExecBro (npm · execbro)

Gives AI agents eyes and hands into running React Native apps: logs, REPL, tap, screenshots

- Trust score: 67/100 (medium)
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-08-03

## Components

- npm · `execbro`: 67/100 (this document), [markdown](https://verifymcp.io/servers/com-execbro-execbro/execbro.md), [page](https://verifymcp.io/servers/com-execbro-execbro/execbro)

## Channel facts

- Registry: `npm`
- Package: `execbro`
- Version: `2.5.0`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-08-03.

- **Supply Chain Security**: 71/100
  - No malware found by supply-chain analysis.
  - CVE check failed: a known high-severity CVE affects sharp 0.34.5, a direct dependency. A fixed version is available.
  - No install/post-install scripts declared.
  - Only part of the dependency tree could be resolved (140 of 144), so this covers what we could see, not the whole tree.
- **Provenance & Transparency**: 97/100
  - Source repository is publicly reachable at the declared URL.
  - Cryptographically verified build provenance (signed, bound to igorzheludkov/execbro).
  - Clear OSI-approved license (MIT).
  - Actively maintained (last published 0 days ago).
  - Disclosure check failed: no security disclosure policy was found in the source repository.
- **Schema Quality & AI Usability**: 52/100
  - AI-judged instruction clarity (good).
  - Context-footprint check failed: tool/resource definitions use about 18851 tokens (~325/item across 58 items; 58 tools + 0 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 5/100
  - Stability check failed: the tool surface changed between 2.2.0 and 2.5.0: 1 tool removals, 0 breaking changes, 0 additions.
- **Tool Coverage**: 100/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 100% of tool parameters carry a description.
- **Capabilities**: 100/100
  - Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.

## Install

### Claude

```bash
claude mcp add com-execbro-execbro -- npx -y execbro
```

### Codex

```bash
codex mcp add com-execbro-execbro -- npx -y execbro
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "com-execbro-execbro": {
      "type": "local",
      "command": [
        "npx",
        "-y",
        "execbro"
      ],
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add com-execbro-execbro --command npx --arg -y --arg execbro
```

### Hermes

```yaml
mcp_servers:
  com-execbro-execbro:
    command: "npx"
    args: ["-y", "execbro"]
```

### Other

```json
{
  "mcpServers": {
    "com-execbro-execbro": {
      "command": "npx",
      "args": [
        "-y",
        "execbro"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-08-03 (score 67, −2)

No change was recorded against any check on this day. Supply Chain Security went from 81 to 71. Other categories moved too: Stability & Change Management rose 3.

### 2026-08-02 (score 69, −2)

- [security regression] Stability: unverified → fail
- [security regression] Provenance: pass → unverified
- [security regression] Install scripts: pass → unverified
- [security regression] Known CVEs: fail → unverified
- [security regression] Provenance: pass → fail
- [security] Stability: Stability not yet verified: we do not have a sandbox capture of the MCP schema this version of the package serves yet.
- [security] The attested source repository moved: igorzheludkov/execbro
- [security] The attested source repository moved: igorzheludkov/execbro
- [functional regression] Capabilities: pass → unverified
- [functional regression] License: pass → unverified
- [functional regression] Tool coverage: 100 → unverified
- [functional regression] Maintenance: pass → unverified
- [functional regression] Dependency health: partial → unverified
- [functional regression] License: pass → fail
- [functional] First check of Schema quality: unverified
- [functional] Schema quality: excellent → good
- [functional] Licence: MIT
- [functional] Licence: MIT
- [functional] Package version: 2.2.0 → 2.5.0
- [functional] Package version: 2.2.0 → 2.4.0
- [functional] Package version: 2.2.0 → 2.3.0

### 2026-08-01 (score 71)

First indexed and scored.

## MCP tools (58)

### `activate_license` (~52 tokens)

Activate a Pro license using an activation token from your dashboard. Use this if you signed up on the website and need to link your account to this MCP installation.

Input parameters:

- `token` (string, required): Activation token from your dashboard

### `delete_account` (~65 tokens)

Permanently delete your account and reset this MCP installation. Removes your server-side data and local cache. You will get a new installation ID on next restart. Requires confirm: 'DELETE' to proceed.

Input parameters:

- `confirm` (string): Must be exactly 'DELETE' to confirm account deletion

### `get_usage_guide` (~93 tokens)

Get recommended workflows and best practices for using the debugging tools. Call without parameters to see all available topics with short descriptions. Call with a topic parameter to get the full guide for that topic.

Input parameters:

- `topic` (string): Topic to get the full guide for. Available topics: setup, inspect, layout, interact, logs, network, state, bundle, feedback. Omit to see the overview of all topics.

### `get_license_status` (~53 tokens)

Get your installation ID, license tier, and this month's usage against the free cap. Shows the Installation ID (needed to link Pro in the dashboard), current tier, cache validity, and calls used / remaining this month.

### `send_feedback` (~304 tokens)

Report feedback about the ExecBro MCP tools THEMSELVES — a tool (tap, get_screen_layout, get_logs, etc.) that behaved incorrectly, was confusing, was missing, or could work better. This is EXCLUSIVELY about your experience operating ExecBro's debugging tools. It is NOT for bugs in the user's app under test, and NOT for the feature or task you were working on in this session — keep that out of the report entirely. Auto-collects environment info. Returns a pre-filled GitHub issue URL and formatted issue body. Ask the user to open the URL and paste the body to submit.

Input parameters:

- `description` (string, required): What about ExecBro's tools went wrong or could be better: which tool, what you expected it to do, what it actually did. Do NOT describe the app feature or task you were debugging — only the tool's be…
- `title` (string, required): Short summary of the ExecBro tooling issue (becomes the GitHub issue title)
- `type` (string, required): Type, scoped to ExecBro tooling: "bug" = an ExecBro tool malfunctioned, "feature_request" = a missing ExecBro capability, "feedback" = general notes on using the ExecBro tools
- `workflow_context` (string): Which ExecBro tools were in use when the issue surfaced (e.g. "tap → get_screen_layout retry loop"). Name the tools and the debugging step — not the user's app goal.

### `redux_dispatch` (~468 tokens)

Dispatch a Redux action to the store bound to the app's <Provider>, triggering useSelector subscribers and React re-renders. Resolves the live store by walking the React fiber tree on each call (no SDK setup needed; works even if no store was registered with init()).
PURPOSE: Drive state-controlled UI (loaders, modals, toasts, error overlays) without exercising the real flow (network, OTP, etc.).
WHY THIS EXISTS: __RN_AI_DEVTOOLS__.stores.redux often holds a different store reference than the one passed to <Provider>, so dispatching through it updates state but does NOT notify react-redux subscribers. This tool dispatches through the actual Provider store, so views re-render.
WHEN TO USE: Verify state-driven UI by seeding redux state directly. Example: dispatch app/setIsLoading: true, then ios_screenshot to confirm the loader rendered.
WORKFLOW: redux_dispatch({ action: { type: 'app/setIsLoading', payload: true } }) -> ios_screenshot -> redux_dispatch({ action: { type: 'app/setIsLoading', payload: false } }).
LIMITATIONS: Requires React DevTools hook (dev mode). Action must be plain JSON-serializable (no thunks/functions). If the app has multiple <Provider> roots, pass storeIndex (default 0).
GOOD: redux_dispatch({ action: { type: 'app/setIsLoading', payload: true } })
BAD: redux_dispatch({ action: () => ... }) — actions must be plain objects; for thunks use execute_in_app to call your action creator.

Input parameters:

- `action` (object, required): Plain JSON-serializable Redux action object, e.g. { type: 'app/setIsLoading', payload: true }.
- `device` (string): RN device name (substring). Omit for default; see get_apps.
- `returnPath` (string): Optional dotted path into the post-dispatch state to return for verification (e.g. 'app' or 'auth.user'). Omit to skip returning state — keeps the response small. Use redux_get_state for ad-hoc reads.
- `storeIndex` (integer): Index of the Provider store to dispatch to when the app has multiple <Provider> roots (default: 0).

### `redux_get_state` (~273 tokens)

Read state from the Redux store bound to the app's <Provider>, resolved live via the fiber tree (same store redux_dispatch targets).
PURPOSE: Inspect the current app state without relying on __RN_AI_DEVTOOLS__.stores.redux (which may point at a different store instance than the Provider).
WHEN TO USE: Verify state shape before/after redux_dispatch, or check what slice keys exist before crafting an action.
WORKFLOW: redux_get_state() -> craft action -> redux_dispatch -> redux_get_state({ path: 'app' }) to confirm.
LIMITATIONS: Requires React DevTools hook (dev mode). State must be JSON-serializable; non-serializable values are replaced with an error marker.
GOOD: redux_get_state({ path: 'app' })
BAD: redux_get_state({ path: 'app.isLoading.0' }) when isLoading is a boolean — path traversal returns undefined.

Input parameters:

- `device` (string): RN device name (substring). Omit for default; see get_apps.
- `path` (string): Optional dotted path into state (e.g. 'app' or 'auth.user'). Omit for the full state.
- `storeIndex` (integer): Index of the Provider store to read from when the app has multiple <Provider> roots (default: 0).

### `execute_in_app` (~695 tokens)

Execute JavaScript code in the connected React Native app and return the result. Use this for inspecting app state, calling methods on exposed global objects, or running diagnostic code. Hermes compatible: 'global' is automatically polyfilled to 'globalThis', so both global.__REDUX_STORE__ and globalThis.__REDUX_STORE__ work.

RECOMMENDED WORKFLOW: 1) list_debug_globals to discover available objects, 2) inspect_global to see properties/methods, 3) execute_in_app to call methods or read values.

LIMITATIONS (Hermes engine):
\- NO require() or import — only pre-existing globals are available
\- Async: use `Promise.resolve(foo()).then(function(r){ return r; })` (resolved for you when awaitPromise:true). `async`/`await` syntax is engine-dependent — many Hermes builds reject it.
\- Multi-statement input is auto-wrapped into an IIFE returning the last statement's value. If that can't yield a value (`if`/`for`/declaration), write the IIFE yourself with an explicit `return`.
\- Non-ASCII in string literals (emoji, Arabic, CJK) is auto-escaped server-side. Write it as-is.

GOOD examples: `__DEV__`, `__APOLLO_CLIENT__.cache.extract()`, `__EXPO_ROUTER__.navigate('/settings')`
BAD examples: `await fetch(...)` (bare top-level await), `require('react-native')`
Pass timeoutMs (ms) for long-running expressions; capped at 120000. Auto-reconnect surfaces _meta.reconnected when a transport drop was self-healed.

Input parameters:

- `awaitPromise` (boolean): Whether to await promises (default: true)
- `collect` (string): Collect a deferred promise result by handle. When a promise outlives its poll budget the result is kept in the app and its handle returned; pass it here to retrieve the settled value. Use instead of…
- `device` (string): RN device name (substring). Omit for default; see get_apps.
- `expression` (string): JavaScript expression to execute. Must be valid Hermes syntax — no require(), no `await`/`async` (use `Promise.resolve(foo()).then(function(r){ return r; })`), no unbalanced quotes. Multi-statement i…
- `maxResultLength` (number): Target size of the result in characters (default: 2000, 0 for unlimited). Oversized results are bounded structurally — arrays and objects are elided with count-preserving markers like "…+150 more" —…
- `timeoutMs` (number): Per-call timeout in milliseconds. Default: 10000. Hard cap: 120000 (values above are clamped with a warning surfaced in the response). A timeout here is a logical failure and does NOT trigger auto-re…
- `verbose` (boolean): Disable result truncation. Tip: Be cautious - Redux stores or large state can return 10KB+.

### `list_debug_globals` (~321 tokens)

List globally available debugging objects in the connected app (Apollo, Redux, React DevTools, etc.).
PURPOSE: Enumerate the app's globalThis.* surface so you know which stores, clients, and debug hooks you can drill into.
WHEN TO USE: Start of a state-debugging session, or when you don't know whether the app exposes a Redux/Apollo/Zustand handle.
WORKFLOW: list_debug_globals -> inspect_global(objectName="...") -> execute_in_app for reads/mutations.
SDK INTEGRATION: When react-native-ai-devtools-sdk's init({ stores, navigation, custom }) was called, the response includes an sdk.paths array of dotted paths (e.g. __RN_AI_DEVTOOLS__.stores.redux). Pass them to inspect_global or execute_in_app.
RN NAMESPACE: The rn field reports globalThis.__rn__ — a curated set of seven RN modules (I18nManager, PixelRatio, Platform, StyleSheet, AppRegistry, NativeModules, Dimensions) populated by SDK exposeRnGlobals() or the executor's fallback bootstrap. Use paths like __rn__.Platform.OS. rn=null → bootstrap not yet run; keys=[] → ran but no match.
OUTPUT: { sdk: {...}|null, rn: {keys, hint}|null, categories: {...} }
LIMITATIONS: Only sees variables explicitly assigned to a global. Module-scoped state is invisible — expose it first or use the SDK.

Input parameters:

- `device` (string): RN device name (substring). Omit for default; see get_apps.

### `inspect_global` (~374 tokens)

Inspect a global object (or a dotted path into one) to see its properties, types, and whether they are callable functions. Use this BEFORE calling methods on unfamiliar objects to avoid errors.
PURPOSE: Surface the shape of a global (Apollo client, Redux store, Expo Router, SDK-registered store, etc.) — keys, types, and which members are callable — without executing arbitrary code.
WHEN TO USE: After list_debug_globals identifies a promising global and before you try execute_in_app on it.
WORKFLOW: list_debug_globals -> inspect_global(objectName="__APOLLO_CLIENT__") -> execute_in_app("__APOLLO_CLIENT__.cache.extract()").
DOTTED PATHS: Pass dotted paths to drill into the SDK surface, e.g. inspect_global({ objectName: "__RN_AI_DEVTOOLS__.stores.redux" }) or "__RN_AI_DEVTOOLS__.custom.mmkv". Only identifier paths are accepted — for arbitrary expressions, use execute_in_app.
LIMITATIONS: Only reads one level deep; nested objects show as a 100-char JSON preview — re-inspect the child path. Returns an error object (not a throw) when the path doesn't resolve.
GOOD: inspect_global({ objectName: "__APOLLO_CLIENT__" }) | inspect_global({ objectName: "__RN_AI_DEVTOOLS__.stores.redux" })
BAD: inspect_global({ objectName: "store.getState()" }) — call expressions aren't supported; use execute_in_app.

Input parameters:

- `device` (string): RN device name (substring). Omit for default; see get_apps.
- `objectName` (string, required): Identifier or dotted path of the global to inspect (e.g., '__APOLLO_CLIENT__', '__RN_AI_DEVTOOLS__.stores.redux', '__RN_AI_DEVTOOLS__.custom.mmkv')

### `get_refresh_status` (~351 tokens)

Pull-style probe: did the JS runtime accept a Fast Refresh (HMR) update since `since`? Returns lastUpdateAt, updateCount, and recentUpdates from a 32-entry ring buffer fed by a recorder around __ReactRefresh.performReactRefresh (preferred) or \$RefreshReg\$ (fallback).

PURPOSE: Confirm an edit landed in the running app without polling logs or screenshots. After editing TSX, wait ~2s then call with `since` = a Date.now() captured BEFORE the edit. updateCount > 0 means Fast Refresh accepted.

WHEN TO USE: After editing .tsx/.ts files (prefer over reload_app). To distinguish runtime acceptance (this) from Metro build state (get_bundle_status).

FILTER: `since` (epoch ms) keeps entries with at > since. `sincePath` (substring) matches modulePath — available on the \$RefreshReg\$ path but often omitted on performReactRefresh; if so the filter matches nothing — drop it.

LIMITATIONS: A full reload resets the buffer (next call reports `recorder just installed`). Edits to non-React utility files still increment. Requires the React 18+ refresh runtime.

SEE ALSO: get_bundle_status (did Metro compile?), get_bundle_errors (compile failures), reload_app (force full reload).

Input parameters:

- `device` (string): RN device name (substring). Omit for default; see get_apps.
- `since` (number): Epoch ms; only count refresh entries with at > since. Capture Date.now() before your edit.
- `sincePath` (string): Substring matched against entry modulePath. Must not contain double quotes. Omit on builds where modulePath is unavailable.

### `get_logs` (~637 tokens)

Retrieve console logs from connected React Native app. Tip: Use summary=true first for a quick overview (counts by level + last 5 messages), then fetch specific logs as needed.
PURPOSE: Pull captured console output (log/warn/error/info/debug) from the in-memory buffer, and optionally native device logs (crashes, ANRs) via source="native".
WHEN TO USE: Start of any log-driven investigation, verifying a code change picked up via Fast Refresh, or confirming a reported error actually fires.
WORKFLOW: scan_metro -> get_logs(summary=true) -> narrow with search_logs(text="...") or get_logs(level="error") -> clear_logs between reproductions.
LIMITATIONS: Circular buffer (~500 entries). Only captures logs emitted after the app connected; pre-connect logs are lost.
GOOD: get_logs({ summary: true }) then get_logs({ level: "error", maxLogs: 20 })
BAD: get_logs({ maxLogs: 500, verbose: true }) as a first call — floods context; start with summary=true.

Input parameters:

- `device` (string): RN device name (substring). Omit for all devices; see get_apps.
- `epoch`: Filter by app run. 'current' = the live run only; a number targets a specific run; omit or 'all' for everything including pre-restart data (default).
- `kind` (string): Filter native/merged events by kind. Omit for all kinds.
- `level` (string): Filter by log level (default: all)
- `maxLogs` (number): Maximum number of logs to return (default: 50)
- `maxMessageLength` (number): Max characters per message (default: 500, set to 0 for unlimited). Tip: Use lower values for overview, higher when debugging specific data structures.
- `minLevel` (string): Relevance floor for native events (default: warn). Crashes and ANRs are always returned regardless. Lower to 'debug' to see native library loading.
- `since` (string): Native acquisition window — ISO timestamp or a duration like "5m". Widens the device query; already-seen events are still deduped.
- `source` (string): Which log stream to read. 'js' (default) = console output over CDP, instant. 'native' = device logs (Android logcat / iOS os_log) filtered to this app — surfaces crashes, ANRs and OOM kills that neve…
- `startFromText` (string): Start from the first log line containing this text
- `summary` (boolean): Return summary statistics instead of full logs (count by level + last 5 messages). Use for quick overview.
- `verbose` (boolean): Disable all truncation and return full messages. Tip: Use with lower maxLogs (e.g., 10) to avoid token overload when inspecting large objects.

### `search_logs` (~242 tokens)

Search console logs for text (case-insensitive).
PURPOSE: Find log lines matching a substring across the connected app's console buffer.
WHEN TO USE: User reports a known error/warning, or wants to trace a specific event (e.g., "redux", "auth failed"). For unfocused exploration, prefer get_logs.
WORKFLOW: scan_metro -> search_logs(text="...") -> if empty, get_logs to verify buffer populated.
LIMITATIONS: Only matches text captured AFTER the app connected; won't find pre-connect logs.
GOOD: search_logs({ text: "TypeError" })
BAD: search_logs({ text: "" })  (use get_logs for a raw dump)

Input parameters:

- `device` (string): RN device name (substring). Omit for all devices; see get_apps.
- `maxMessageLength` (number): Max characters per message (default: 500, set to 0 for unlimited)
- `maxResults` (number): Maximum number of results to return (default: 50)
- `text` (string, required): Text to search for in log messages
- `verbose` (boolean): Disable all truncation and return full messages

### `clear_logs` (~142 tokens)

Clear the log buffer.
PURPOSE: Empty the in-memory console buffer (and the SDK buffer if installed) so the next get_logs / search_logs only sees fresh entries.
WHEN TO USE: Before reproducing a bug so the resulting logs are isolated; between test iterations to avoid noise from earlier runs.
WORKFLOW: clear_logs -> reproduce the issue (tap / navigate / reload_app) -> get_logs or search_logs.
GOOD: clear_logs() right before tap(text="Submit")
BAD: clear_logs() AFTER the repro — you just deleted the evidence.

Input parameters:

- `device` (string): RN device name (substring). Omit for all devices; see get_apps.

### `get_log_details` (~241 tokens)

Get the full payload of a single log event — complete stack trace, backtrace, or oversized message.
PURPOSE: Expand one row from get_logs into its full text. A crash row collapses a 60-line backtrace; this returns all of it.
WHEN TO USE: After get_logs shows an event you need to read in full (a crash, an exception, a large payload).
WORKFLOW: get_logs -> copy the id (e.g. "n7") -> get_log_details(id="n7").
LIMITATIONS: Ids are valid until that device's buffer rolls over or clear_logs runs, not for the whole server session — call get_logs again to get fresh ones. Reads the buffer — it does not re-query the device.
GOOD: get_log_details({ id: "n7" })
BAD: Guessing ids — always take them from get_logs.

Input parameters:

- `id` (string, required): Event id from get_logs (e.g. "n7")
- `maxLength` (number): Max characters of payload (default: 4000, 0 for unlimited)
- `verbose` (boolean): Disable truncation entirely

### `get_network_requests` (~448 tokens)

Retrieve captured network requests from connected React Native app. Shows URL, method, status, and timing. Note: On Bridgeless targets (Expo SDK 52+) without the SDK, capture may miss early startup requests. Install execbro-sdk for full capture with headers and response bodies. Tip: Use summary=true first for stats overview.
PURPOSE: Inspect HTTP traffic the app made since connection — URLs, methods, status codes, and timings — to debug API, auth, and caching issues.
WHEN TO USE: User reports a failed login/load, slow screen, or wrong data. Confirm a request fired, check its status, and pivot to get_request_details for headers/body.
WORKFLOW: scan_metro -> reproduce action -> get_network_requests({ summary: true }) -> get_network_requests({ status: 500 }) or search_network -> get_request_details(id).
LIMITATIONS: Bridgeless targets without the SDK may miss pre-connect requests and response bodies — install execbro-sdk for full fidelity.
GOOD: get_network_requests({ summary: true }) then get_network_requests({ urlPattern: "/login", status: 401 })
BAD: get_network_requests({ maxRequests: 500 }) as the first call — start with summary=true.

Input parameters:

- `device` (string): RN device name (substring). Omit for all devices; see get_apps.
- `epoch`: Filter by app run. 'current' = the live run only; a number targets a specific run; omit or 'all' for everything including pre-restart data (default).
- `maxRequests` (number): Maximum number of requests to return (default: 50)
- `method` (string): Filter by HTTP method (GET, POST, PUT, DELETE, etc.)
- `status` (number): Filter by HTTP status code (e.g., 200, 401, 500)
- `summary` (boolean): Return statistics only (count, methods, domains, status codes). Use for quick overview.
- `urlPattern` (string): Filter by URL pattern (case-insensitive substring match). Also matches GraphQL operation names (e.g. "GetCharacters"), since every GraphQL call shares one URL.

### `search_network` (~249 tokens)

Search network requests by URL pattern (case-insensitive).
PURPOSE: Filter the network buffer to requests whose URL matches a substring — fast way to find a specific endpoint in a noisy app.
WHEN TO USE: You know part of the URL (e.g., "/graphql", "users", a domain) and want matching requests across all devices.
WORKFLOW: search_network(urlPattern="/api/") -> get_request_details(requestId="...") for full headers/body.
LIMITATIONS: Matches URL only; for method/status/body filtering use get_network_requests. Bodies are only present when the SDK is installed.
GOOD: search_network({ urlPattern: "/graphql" })
BAD: search_network({ urlPattern: "" }) — empty pattern matches everything; use get_network_requests instead.

Input parameters:

- `device` (string): RN device name (substring). Omit for all devices; see get_apps.
- `maxResults` (number): Maximum number of results to return (default: 50)
- `urlPattern` (string, required): URL pattern to search for. Also matches GraphQL operation names (e.g. "GetCharacters") — use the operation name to find one GraphQL call among many sharing the same endpoint.

### `get_request_details` (~303 tokens)

Get full details of a specific network request including headers, body, and timing. With the SDK installed, includes full request/response bodies. Without SDK, bodies are not available on most targets. Use get_network_requests first to find the request ID.
PURPOSE: Drill into a single network entry — full request/response headers, body, status, and timing breakdown.
WHEN TO USE: After get_network_requests or search_network returns a suspect ID and you need the payload to diagnose.
WORKFLOW: get_network_requests / search_network -> copy id -> get_request_details(requestId).
LIMITATIONS: Bodies require the execbro-sdk in the app; on CDP-only targets response bodies are missing. Large bodies are truncated — raise maxBodyLength.
GOOD: get_request_details({ requestId: "42", maxBodyLength: 4000 })
BAD: Guessing requestIds — always get them from get_network_requests / search_network first.

Input parameters:

- `device` (string): RN device name (substring). Omit for all devices; see get_apps.
- `maxBodyLength` (number): Max characters for request body (default: 500, set to 0 for unlimited). Tip: Large POST bodies (file uploads, base64) can be 10KB+.
- `requestId` (string, required): The request ID to get details for
- `verbose` (boolean): Disable body truncation. Tip: Use when you need to inspect full JSON payloads.

### `clear_network` (~152 tokens)

Clear the network request buffer.
PURPOSE: Reset the captured request list to isolate new traffic from a specific user action.
WHEN TO USE: Right before reproducing a bug so the buffer contains only the relevant requests.
WORKFLOW: clear_network -> trigger action (tap, execute_in_app) -> get_network_requests / search_network.
LIMITATIONS: Irreversible — cleared requests cannot be recovered. Also clears the SDK's in-app buffer when SDK is present.
GOOD: clear_network() before a reproduction.
BAD: Using clear_network as a workaround for stale connections — use scan_metro / ensure_connection instead.

Input parameters:

- `device` (string): RN device name (substring). Omit for all devices; see get_apps.

### `app_request` (~370 tokens)

Issue an HTTP request from inside the running app, as the logged-in user.
PURPOSE: Probe your backend through the app's real network stack, TLS trust and proxy config — without pasting credentials into the conversation.
WHY THIS EXISTS: hand-written fetch calls either dig the token out of redux or embed a JWT literal in the expression, which puts the credential in the transcript. auth="auto" resolves it in-app instead.
WHEN TO USE: reproduce a 4xx, check what an endpoint returns for an edge case, clean up test records the UI can't reach.
WORKFLOW: app_request({ method: "GET", url: "https://api.example.com/me" }) -> inspect status + body.
LIMITATIONS: needs a connected app. Token lookup covers state.user.accessToken, state.auth.accessToken and state.auth.token; pass an explicit Authorization header if your app stores it elsewhere.
GOOD: app_request({ method: "DELETE", url: "https://api.example.com/address/17" })
BAD: embedding a bearer token in an execute_in_app expression — it lands in the transcript.

Input parameters:

- `auth` (string): "auto" (default) resolves a bearer token from app state; "none" sends no Authorization header.
- `body`: JSON-serialisable request body. Sets Content-Type: application/json unless you override it.
- `device` (string): Target device name (substring match). Omit for the default device.
- `headers` (object): Extra request headers. An explicit Authorization here wins over auth="auto".
- `maxResultLength` (number): Target size for the returned body in characters (default 25000). Oversized bodies are bounded structurally.
- `method` (string, required): HTTP method.
- `url` (string, required): Absolute URL to request.

### `navigate` (~323 tokens)

Navigate the app's router directly, and verify it actually moved.
PURPOSE: Jump to a screen without tapping through the UI, with a settled before/after route check.
WHY THIS EXISTS: hand-written router calls report success whenever nothing throws. A path sent to a React Navigation ref changes nothing and warns only in LogBox, so a no-op reads as a success.
DESTINATIONS ARE NOT INTERCHANGEABLE: Expo Router takes paths ("/event-details?id=1"); React Navigation takes route names ("TarotNav"). The response reports which router resolved. Unknown React Navigation names are rejected before dispatch, with nearest-match suggestions.
WORKFLOW: navigate({ to: "/settings" }) -> check changed -> get_screen_state.
LIMITATIONS: push/replace are unavailable on a React Navigation root ref (stack-scoped). changed=false means it settled without moving; indeterminate=true means no settled reading.
GOOD: navigate({ to: "TarotNav" }); navigate({ action: "back" })
BAD: navigate({ to: "/TarotNav" }) on React Navigation — that is a path, not a route name.

Input parameters:

- `action` (string): Default "navigate".
- `device` (string): Target device name (substring match).
- `params` (object): Route params, passed as the second navigate argument.
- `routeTable` (boolean): Include the app's registered route names in the response.
- `to` (string): Destination: a path for Expo Router, a route name for React Navigation. Required unless action is "back" or "reset".

### `logbox` (~535 tokens)

Interact with React Native's LogBox overlay (dev mode only). Actions: "dismiss" clears all entries and returns their content. "push" displays a message in the LogBox error banner (visible to the developer watching the device). "ignore" adds patterns to suppress future LogBox entries for this session. "detect" reads current LogBox state without modifying it. Only works in __DEV__ mode — LogBox does not exist in production builds.
PURPOSE: Control RN's on-device red/yellow overlay — clear it when it blocks UI, suppress noisy repeats, or push a message back to the developer.
WHEN TO USE: Screenshot/tap reports LogBox is obstructing the screen, an error banner prevents interaction, or you want to surface info to the dev watching the simulator.
WORKFLOW: logbox(action="detect") -> if present: logbox(action="dismiss") to read + clear -> continue UI work. Use action="ignore" with patterns to stop repeat noise.
LIMITATIONS: Dev-only — no effect in production builds. "push" at level="warning" won't show a banner unless LogBox is already open.
GOOD: logbox({ action: "dismiss" }); logbox({ action: "ignore", patterns: ["[APOLLO]"] })
BAD: Spamming logbox(action="push") for every tool step — annoys the developer.

Input parameters:

- `action` (string, required): Action to perform: "dismiss", "push", "ignore", or "detect"
- `device` (string): RN device name (substring). Omit for default; see get_apps.
- `expanded` (boolean): When true, opens the full-screen LogBox detail view instead of the minimized bottom banner. Useful for important messages with clickable URLs (default: false)
- `level` (string): LogBox level for push (default: "error"). Only "error" shows a visible bottom banner; "warning" is stored but not visually shown unless LogBox is already open
- `message` (string): Message to push into LogBox (required when action="push")
- `patterns` (array): Patterns to ignore (required when action="ignore"), e.g. ["[APOLLO]", "deprecated"]
- `subtitle` (string): Additional info shown in the call stack area when expanded=true (default: "MCP Server"). Use for context like "License Check", "Usage Limit", etc.
- `target` (string): Where to push the message (default: "logbox"). "logbox" shows on device screen, "metro" outputs to Metro terminal via console.log

### `get_bundle_status` (~193 tokens)

Get the current Metro bundler status including build state and any recent bundling errors. Use this to check if there are compilation/bundling errors that prevent the app from loading.
PURPOSE: Snapshot Metro's current build state (idle / transforming / error) together with any captured errors — a fast "is the bundler healthy?" check.
WHEN TO USE: Before diving into runtime debugging — rules out compile-time failures that would make get_logs and tap pointless.
WORKFLOW: get_bundle_status -> if errors present: get_bundle_errors for detail -> fix -> get_bundle_errors({ clear: true }).
LIMITATIONS: Relies on Metro's WebSocket event stream; if Metro isn't running or the connection dropped, status may be stale.
GOOD: get_bundle_status() at the start of a debug session.
BAD: Polling every second — Metro events are push-based; just call once and act on the result.

### `get_bundle_errors` (~386 tokens)

Retrieve captured Metro bundling/compilation errors. These are errors that occur during the bundle build process (import resolution, syntax errors, transform errors) that prevent the app from loading. If no errors are captured but Metro is running without connected apps, automatically falls back to screenshot+OCR to capture the error from the device screen.
PURPOSE: Surface Metro's build-time failures (not runtime JS errors) that keep the app from booting or hot-reloading.
WHEN TO USE: App shows the red error screen, refuses to connect, or Fast Refresh stops working after an edit. Also use when get_logs is silent but the app is clearly broken.
WORKFLOW: get_bundle_status -> get_bundle_errors -> fix source -> get_bundle_errors({ clear: true }) -> reload_app.
LIMITATIONS: Captures errors Metro emits via its WebSocket; the screenshot+OCR fallback requires a booted simulator and the platform param.
GOOD: get_bundle_errors({ platform: "ios" }); get_bundle_errors({ clear: true }) after fixing, so the next read reflects only new errors.
BAD: Using get_bundle_errors to look for runtime TypeErrors — those live in get_logs, not the bundler.

Input parameters:

- `clear` (boolean): If true, empty the bundle error buffer after reading it. Use once a bundling error is fixed so the next call reflects only fresh errors. The errors are still returned in this response.
- `deviceId` (string): Optional device target for screenshot fallback. Accepts an adb serial / iOS UDID, an emulator/simulator name, or a substring of the connected RN device name. Uses first available device if not specif…
- `maxErrors` (number): Maximum number of errors to return (default: 10)
- `platform` (string): Platform for screenshot fallback when no errors are captured via CDP. Required to enable fallback.

### `list_devices` (~225 tokens)

List every iOS simulator, Android emulator, and connected physical device on the host machine, in one structured response.
PURPOSE: Single discovery entry point. Returns booted+shutdown iOS sims (from simctl), running+stopped Android emulators (from `emulator -list-avds` cross-referenced with `adb devices`), and attached physical devices. Each row is enriched with `rnConnected` when an RN app from get_apps matches the same identifier.
WHEN TO USE: Before tap/swipe to pick a device, when a tool reports an ambiguous-device error, or to check whether a simulator is booted before targeting it.
WORKS WITHOUT RN: No Metro connection required. Safe to call before scan_metro.
WORKFLOW: list_devices -> tap({ device: '<udid-or-serial-or-name>', ... })
SEE ALSO: get_apps for RN-specific connection details (RN version, JS engine, network capture mode).

Input parameters:

- `refresh` (boolean): Force re-query of simctl/adb/emulator instead of returning cached results (5s TTL).

### `android_launch_app` (~167 tokens)

Launch an app on an Android device/emulator by package name
[PLATFORM-SPECIFIC — no cross-platform equivalent; use when launching an Android app by package name]
PURPOSE: Start an installed Android app by its package (and optional activity) so the next tool calls hit a running process.
WHEN TO USE: After a force-stop or install, or when the app isn't foregrounded before interaction.

Input parameters:

- `activityName` (string): Optional activity name to launch (e.g., .MainActivity). If not provided, launches the main activity.
- `deviceId` (string): Android target: adb serial, emulator name, or RN device substring. Omit for first.
- `packageName` (string, required): Package name of the app (e.g., com.example.myapp)

### `android_list_packages` (~131 tokens)

List installed packages on an Android device/emulator
[PLATFORM-SPECIFIC — no cross-platform equivalent; use when listing installed Android packages]
PURPOSE: Enumerate package names visible to adb so you can confirm installation or pick the right target for android_launch_app.
WHEN TO USE: Before android_launch_app when you don't know the exact package name, or to verify an install succeeded.

Input parameters:

- `deviceId` (string): Android target: adb serial, emulator name, or RN device substring. Omit for first.
- `filter` (string): Optional filter to search packages by name (case-insensitive)

### `ios_launch_app` (~139 tokens)

Launch an app on an iOS simulator by bundle ID
[PLATFORM-SPECIFIC — no cross-platform equivalent; use when launching an iOS app by bundle ID]
PURPOSE: Start an installed iOS app by its bundle ID so the next tool calls hit a running process.
WHEN TO USE: After ios_terminate_app or an install, or when the app isn't foregrounded before interaction.

Input parameters:

- `bundleId` (string, required): Bundle ID of the app (e.g., com.example.myapp)
- `udid` (string): iOS target: UDID, simulator name, or RN device substring. Omit for booted.

### `ios_open_url` (~249 tokens)

Open a URL in the iOS simulator (opens in default handler or Safari).
PURPOSE: Drive an iOS simulator into a deep link or universal link entry point so you can exercise routing from an external entry.
WHEN TO USE: Testing deep-link handlers, universal link routing, OAuth/SSO callback URLs, or any flow that enters the app via a URL.
WORKFLOW: ios_boot_simulator -> ios_launch_app (or have the app running) -> ios_open_url -> ios_screenshot / get_screen_layout to verify the target screen rendered.
GOOD: ios_open_url(url="myapp://product/42") to land directly on a product screen.
BAD: ios_open_url(url="...") used as a substitute for in-app navigation when the user would normally tap — prefer `tap` for normal interaction flows.

[PLATFORM-SPECIFIC — no cross-platform equivalent; use when testing iOS deep links or universal links]

Input parameters:

- `udid` (string): iOS target: UDID, simulator name, or RN device substring. Omit for booted.
- `url` (string, required): URL to open (e.g., https://example.com or myapp://path)

### `ios_terminate_app` (~133 tokens)

Terminate a running app on an iOS simulator
[PLATFORM-SPECIFIC — no cross-platform equivalent; use when force-terminating an iOS app]
PURPOSE: Force-kill an iOS app process so the next launch starts from a cold state.
WHEN TO USE: To reset app state fully (beyond what reload_app does), or before reinstalling a new build.

Input parameters:

- `bundleId` (string, required): Bundle ID of the app to terminate
- `udid` (string): iOS target: UDID, simulator name, or RN device substring. Omit for booted.

### `ios_boot_simulator` (~107 tokens)

Boot an iOS simulator by UDID.
PURPOSE: Bring a specific simulator online so you can install/launch an app in it.
WHEN TO USE: At session start when no simulator is running, or after switching between device models.

[PLATFORM-SPECIFIC — no cross-platform equivalent; use when booting an iOS simulator] Use list_devices to find available simulators.

Input parameters:

- `udid` (string, required): UDID of the simulator to boot (from list_devices)

### `scan_metro` (~226 tokens)

Scan for running Metro bundler servers and automatically connect to any found React Native apps. This is typically the FIRST tool to call when starting a debugging session - it establishes the connection needed for other tools like get_logs, list_debug_globals, execute_in_app, and reload_app.
PURPOSE: Discover Metro on ports 8081-8090 and auto-connect all React Native debugger targets it advertises.
WHEN TO USE: At the start of any session, or after the user restarts Metro / boots a new simulator.
WORKFLOW: scan_metro -> get_apps -> get_logs / ios_screenshot / tap.
GOOD: scan_metro()
BAD: scan_metro() called repeatedly in a loop — use ensure_connection to re-verify an existing connection.

Input parameters:

- `endPort` (number): End port for scanning (default: 8090). Metro allocates upward from 8081, so ten ports covers several apps side by side; widen only if your bundler is pinned elsewhere.
- `startPort` (number): Start port for scanning (default: 8081)

### `ensure_connection` (~240 tokens)

Verify or establish a healthy connection to a React Native app. Use before running commands if connection may be stale, or after navigation/reload. This tool runs a health check and will auto-reconnect if needed.
PURPOSE: Health-check the existing CDP connection and transparently reconnect if it has gone stale, without rescanning all Metro ports.
WHEN TO USE: After a suspected disconnect (silent gaps, reload_app, app crash) or before long-running flows where a mid-flow drop would be costly. Cheaper than scan_metro when you already connected once this session.
WORKFLOW: scan_metro (once) -> ensure_connection(healthCheck=true) -> resume tool calls. Use forceRefresh=true if the first probe still looks dead.
GOOD: ensure_connection({ healthCheck: true })
BAD: ensure_connection() before scan_metro has ever run — call scan_metro first.

Input parameters:

- `forceRefresh` (boolean): Force close existing connection and reconnect (default: false)
- `healthCheck` (boolean): Run health check to verify page context is responsive (default: true)
- `port` (number): Metro port (default: auto-detect)

### `get_apps` (~172 tokens)

List currently connected React Native apps and their connection status. If no apps are connected, run scan_metro first to establish a connection.
PURPOSE: Enumerate active debug targets with device names, platforms, ports, and detected RN/Expo versions so you can target the right one.
WHEN TO USE: After scan_metro to confirm what connected, or before passing a device="..." filter to another tool.
WORKFLOW: scan_metro -> get_apps -> get_logs / ios_screenshot / tap (with device="..." if multiple).
LIMITATIONS: Only lists devices the MCP has successfully connected to — stale targets don't appear here, use get_connection_status for health details.
GOOD: get_apps()
BAD: Calling get_apps in a tight loop — the list doesn't change without a scan_metro or disconnect_metro.

### `get_connection_status` (~298 tokens)

Get detailed connection health status including uptime, recent disconnects/reconnects, and connection gaps that may indicate missing data.
PURPOSE: Diagnose flaky CDP sessions — quantify uptime, count reconnects, and expose gaps where logs/network data could be missing.
WHEN TO USE: When logs look suspiciously empty, tools complain about disconnect/reconnect, or the app was suspended and resumed.
WORKFLOW: get_connection_status -> if unhealthy: disconnect_metro -> scan_metro to rebuild a clean session.
LIMITATIONS: Reports only MCP-side view; doesn't know why Metro dropped the socket (simulator sleep, app backgrounded, etc).
GOOD: get_connection_status() after noticing stale data.
BAD: Polling get_connection_status as a heartbeat — use ensure_connection(healthCheck=true) for a live probe.
Pass events=true to include the recent connection event log (connect/close/reconnect lifecycle) — useful when a target drops mid-session and you need to see why reconnect didn't recover it.

Input parameters:

- `eventAppKey` (string): When events=true, filter the event log to a single appKey (format: '<port>-<deviceId>').
- `eventLimit` (number): When events=true, show only the last N events. Default: 50.
- `events` (boolean): Include the recent connection event log (lifecycle: connect, close, reconnect attempts/failures, stale-target, etc). Default: false.

### `connect_metro` (~142 tokens)

Connect to a Metro server on a specific port — the only way to reach a port scan_metro does not probe.
PURPOSE: Establish a CDP WebSocket connection to a Metro server on a known port.
WHEN TO USE: Metro is on a port outside 8081-8090. For any port in that range, scan_metro is strictly better — it probes them all and attaches every Bridgeless target in one call, where this connects to one port only.
SEE ALSO: scan_metro for auto-discovery; get_apps afterwards to confirm the device attached.

Input parameters:

- `port` (number): Metro server port (default: 8081)

### `disconnect_metro` (~241 tokens)

Disconnect from Metro servers and stop auto-reconnection. Without device param: disconnects ALL devices. With device param: disconnects only the matching device. Use this to remove stale connections or free the CDP slot for the built-in debugger. Log and network buffers are preserved. Reconnect later with scan_metro.
PURPOSE: Cleanly release CDP slots so another debugger (Flipper, React DevTools, Chrome) can attach, or nuke a stale connection the MCP keeps reviving.
WHEN TO USE: Before launching a native debugger, when connections keep flapping, or after a simulator/device restart left zombie targets.
WORKFLOW: disconnect_metro -> attach other debugger / restart app -> scan_metro to reconnect.
LIMITATIONS: Suppresses auto-reconnect until scan_metro or connect_metro is called again; buffers persist but won't receive new events.
GOOD: disconnect_metro(); disconnect_metro({ device: "iPhone" })
BAD: Using disconnect_metro to clear logs — use clear_logs instead; disconnect breaks capture.

Input parameters:

- `device` (string): RN device name (substring). Omit for all devices; see get_apps.

### `reload_app` (~343 tokens)

Reload the React Native app (triggers JavaScript bundle reload like pressing 'r' in Metro).
PURPOSE: Force a full JS bundle reload when Fast Refresh isn't enough — clears in-memory state and re-runs the bundle from scratch.
WHEN TO USE (only these cases): (1) native code, app.json, Info.plist, Podfile, or a native module changed; (2) Fast Refresh visibly failed (red-screen or stale render confirmed via screenshot after a few seconds); (3) the app is in a broken state; (4) you need to reset app state completely; (5) the user explicitly asks.
AVOID: reloading reflexively after JS/TS/TSX/style edits — Fast Refresh applies those in 1-2s. A reload discards navigation stack, context, hooks state, BLE/WebSocket connections, paired devices, and auth sessions, which can force re-pairing or re-login and break your verification loop.
WORKFLOW: screenshot → wait 2s for Fast Refresh → if still stale, reload_app. Auto-connects to Metro if no connection exists (with or without a device argument) — no need to call scan_metro first. After reload, wait a few seconds before running other tools.
SEE ALSO: get_refresh_status (did Fast Refresh accept?), get_bundle_status (did Metro compile?).

Input parameters:

- `device` (string): Target device name, substring match against the name shown by get_apps (a simulator UDID or adb serial also works). OMIT THIS unless several devices are connected — passing a name copied from list_de…

### `ios_screenshot` (~396 tokens)

Take a screenshot from an iOS simulator. Returns the image plus a screen-state summary: active route (name + navigation stack), overlay-grouped tappable elements (pressables behind an open sheet/modal are excluded), component names as JSX tags, labels, testIDs, and frames — all in ready-to-tap pixel coordinates. Prefer tap(text="...") when text is exact and unique; otherwise use tap(x, y) with coordinates from the list — this is the most reliable way to tap icons or visually-identified elements. Use component names for inspect_component/find_components.
PURPOSE: Snapshot what the user sees on iOS AND get tap-ready pressables + a structured component map in one call.
WHEN TO USE: Any visual verification, before/after comparison, or as the starting point for tapping UI by coordinates.
WORKFLOW: ios_screenshot -> pick element from pressables -> tap(x, y) or tap(testID=...) -> ios_screenshot to verify.
LIMITATIONS: Requires a booted iOS simulator (simctl). For physical devices or system dialogs without RN, combine with tap(..., native=true).
GOOD: ios_screenshot()
BAD: ios_screenshot({ udid: "guess" }) with a made-up UDID — run list_devices first.
SOURCE: to jump from a pixel to the code that renders it, call inspect_at_point(x, y) — it returns the absolute file and line.

Input parameters:

- `device` (string): Alias for `udid` — same accepted values. Provided for consistency with tap/get_screen_layout/get_screen_state, which all use `device`. If both are given, `udid` wins.
- `outputPath` (string): Optional path to save the screenshot. If not provided, saves to temp directory.
- `udid` (string): iOS target: UDID, simulator name, or RN device substring. Omit for booted.

### `android_screenshot` (~343 tokens)

Take a screenshot from an Android device/emulator. Returns the image plus a screen-state summary: active route (name + navigation stack), overlay-grouped tappable elements (pressables behind an open sheet/modal are excluded), component names as JSX tags, labels, testIDs, and frames — all in ready-to-tap pixel coordinates. Prefer tap(text="...") when text is exact and unique; otherwise use tap(x, y) with coordinates from the list — this is the most reliable way to tap icons or visually-identified elements. Use component names for inspect_component/find_components.
PURPOSE: Snapshot what the user sees on Android AND get tap-ready pressables + a structured component map in one call.
WHEN TO USE: Any visual verification, before/after comparison, or as the starting point for tapping UI by coordinates on Android.
WORKFLOW: android_screenshot -> pick element from pressables -> tap(x, y) or tap(testID=...) -> android_screenshot to verify.
LIMITATIONS: Requires adb in PATH and a running device/emulator. For non-RN surfaces (system dialogs, permission prompts), combine with tap(..., native=true).
GOOD: android_screenshot()
BAD: android_screenshot({ deviceId: "guess" }) with a made-up serial — run list_devices first.
SOURCE: to jump from a pixel to the code that renders it, call inspect_at_point(x, y).

Input parameters:

- `deviceId` (string): Android target: adb serial, emulator name, or RN device substring. Omit for first.
- `outputPath` (string): Optional path to save the screenshot. If not provided, saves to temp directory.

### `get_images` (~355 tokens)

Access the shared image buffer containing screenshots from all tools (ios_screenshot, android_screenshot, ocr_screenshot, tap verification). Returns metadata only by default — use id or groupId+frameIndex to retrieve actual image data. Tap burst verification stores frame groups here when burst=true is used.
PURPOSE: Retrieve prior screenshots — especially tap burst frames — without re-taking them, for visual diffing or reviewing transient UI states.
WHEN TO USE: After tap(burst=true) reports transientChangeDetected, or to compare before/after frames without another screenshot round-trip.
WORKFLOW: tap(burst=true) -> note verification.burstGroupId -> get_images(groupId, frameIndex=N) to inspect individual frames.
LIMITATIONS: Circular buffer (50 entries) — old images are evicted. Metadata is cheap; fetching image data is not — request specific ids, not bulk.
GOOD: get_images({ list: true }); get_images({ groupId: "burst-abc", frameIndex: 2 })
BAD: get_images() with no filter when buffer is full — floods context. Use list:true or last:N first.

Input parameters:

- `clear` (boolean): Clear the buffer
- `frameIndex` (number): Retrieve a specific frame from a group (requires groupId)
- `groupId` (string): List frames in a group (metadata only), or combine with frameIndex to retrieve a specific frame
- `id` (string): Retrieve a specific image by ID (returns image data)
- `last` (number): Return the N most recent entries (metadata only)
- `list` (boolean): List all entries and groups (metadata only, no image data)
- `source` (string): Filter entries by source

### `ocr_screenshot` (~368 tokens)

RECOMMENDED: Use this tool FIRST when you need to find and tap UI elements. Takes a screenshot and extracts all visible text with tap-ready coordinates using OCR. ADVANTAGES over accessibility trees: (1) Works on ANY visible text regardless of accessibility labels, (2) Returns ready-to-use tapX/tapY coordinates - no conversion needed, (3) Faster than parsing accessibility hierarchies, (4) Works consistently across iOS and Android. USE THIS FOR: Finding buttons, labels, menu items, tab bars, or any text you need to tap. Simply find the text in the results and use its tapX/tapY with the tap command.
PURPOSE: Visually locate text on screen and return coordinates safe to pass straight into tap.
WHEN TO USE: Non-RN surfaces, third-party WebViews, accessibility-poor screens, or when fiber/testID strategies have failed.
WORKFLOW: ocr_screenshot(platform="ios") -> scan results for the label -> tap(x=tapX, y=tapY) -> ios_screenshot to verify.
LIMITATIONS: OCR accuracy degrades on very small or stylized text; icons with no label won't appear — use tap(component=...) instead.
GOOD: ocr_screenshot({ platform: "ios" })
BAD: ocr_screenshot used just to view the screen — plain ios_screenshot / android_screenshot is cheaper when you don't need OCR text.
SOURCE: for RN screens, inspect_at_point(x, y) returns the file and line that render an element — no OCR needed.

Input parameters:

- `deviceId` (string): Optional device ID (Android) or UDID (iOS). Uses first available device if not specified.
- `platform` (string, required): Platform to capture screenshot from

### `tap` (~927 tokens)

Tap a UI element. Automatically tries multiple strategies: fiber tree (React), accessibility tree (native), and OCR (visual).
[PRIMARY INTERACTION TOOL — works on iOS and Android; prefer over ios_*/android_* siblings]
PURPOSE: Single unified tap entry point — resolves text/testID/component/coordinates into a real touch event on the correct device.
WHEN TO USE: Any time you need to press a button, focus an input, open a menu, or verify a handler fires. Prefer testID, then text, then component, then (x,y) from a screenshot's pressables list.
WORKFLOW: ios_screenshot or android_screenshot -> tap(testID="...") | tap(text="...") | tap(x, y) -> screenshot again to verify. Use burst=true when meaningful=false but visual feedback looks transient.
LIMITATIONS: iOS needs AXe (brew install cameroncooke/axe/axe) or IDB for accessibility/coordinate taps. Non-ASCII text skips fiber (Hermes); prefer testID. Pass `device` to target a specific simulator/emulator when multiple are available — call list_devices for the inventory.
GOOD: tap({ testID: "login-btn" }); tap({ text: "Submit" }); tap({ x: 300, y: 600 }); tap({ x: 300, y: 600, native: true, device: "emulator-5554" })
BAD: tap({ text: "" }) or tap({ x: 0, y: 0 }) — missing a target. tap({ text: "Submit" }) without first screenshotting an ambiguous screen.
SOURCE: need the file:line that renders an element? inspect_at_point(x, y).

Input parameters:

- `burst` (boolean): Enable burst screenshot capture for enhanced verification. Captures 4 rapid screenshots (~150ms intervals) after the tap to detect transient visual feedback (press animations, highlights, ripples) th…
- `component` (string): Component name match (case-insensitive substring, e.g. 'Button', 'MenuItem').
- `device` (string): Target device. Accepts (a) an iOS simulator UDID, (b) an Android adb serial like 'emulator-5554', (c) the iOS simulator or Android emulator/device name (substring match), or (d) a connected RN app's…
- `index` (number): Zero-based index when multiple elements match (default: 0).
- `maxTraversalDepth` (number): Max parent levels to traverse when searching by component name (default: 15). Increase if your component is deeply wrapped (e.g. inside multiple HOCs/animation wrappers).
- `native` (boolean): When true, tap coordinates directly via ADB/simctl without requiring a React Native connection. Useful for interacting with native UI, system dialogs, or non-RN apps. Requires x/y coordinates.
- `screenshot` (boolean): Return post-tap image bytes in the response. Default true. Set to false to drop the PNG bytes — verification still runs (set verify=false to skip that too). Combine with verify=true to get the meanin…
- `strategy` (string): "auto" (default) tries fiber -> accessibility -> OCR. Set explicitly to skip strategies you know will fail.
- `testID` (string): Exact match on the element's testID prop.
- `text` (string): Visible text to match (case-insensitive substring). ASCII only for fiber strategy; OCR handles non-ASCII.
- `verify` (boolean): Run before/after screenshot diff to detect if the tap had a meaningful visual effect. Default: true for coordinate/accessibility/ocr strategies, false for fiber. Independent of `screenshot` — verify…
- `x` (number): X coordinate in pixels (from screenshot). Must provide both x and y.
- `y` (number): Y coordinate in pixels (from screenshot). Must provide both x and y.

### `android_long_press` (~169 tokens)

Long press at specific coordinates on an Android device/emulator screen
[PLATFORM FALLBACK — prefer `tap` for short taps; keep android_long_press for long-press gestures specifically unless you specifically need native-only behavior]
PURPOSE: Emit a sustained touch at raw pixel coordinates to trigger long-press handlers (context menus, drag starts, multi-select).
WHEN TO USE: Only when a long-press gesture is required — regular taps should go through `tap`.

Input parameters:

- `deviceId` (string): Android target: adb serial, emulator name, or RN device substring. Omit for first.
- `durationMs` (number): Press duration in milliseconds (default: 1000)
- `x` (number, required): X coordinate in pixels
- `y` (number, required): Y coordinate in pixels

### `swipe` (~792 tokens)

Swipe gesture that auto-routes to the correct platform (iOS or Android), with pixel-diff verification.
[PRIMARY INTERACTION TOOL — works on iOS and Android; prefer over ios_*/android_* siblings]
PURPOSE: Single unified swipe entry point. Easiest form: swipe({ direction: "up" }) scrolls to reveal more content ("down"/"left"/"right" also work; bare swipe() defaults to "up"). Optional distance in screenshot pixels (default 33% of axis). For precise control, pass all four coordinates (startX/startY/endX/endY) — they take precedence over direction.
WHEN TO USE: Scrolling lists, paging carousels, pull-to-refresh, dismissing sheets, opening drawers — anything that needs a gesture rather than a tap. Especially useful in virtualized lists (FlatList/SectionList) where off-screen items aren't mounted in the fiber tree.
VERIFICATION: verify=true (default) returns `verification.meaningful` — false means the scroll did nothing (end-of-list, non-scrollable surface, or missed coordinates). burst=true catches transient feedback like overscroll bounce.
WORKFLOW: swipe({ direction: "up" }) -> read response.verification.meaningful. Advanced: pass startX/startY/endX/endY for coordinate-precise gestures.
LIMITATIONS: iOS needs AXe (brew install cameroncooke/axe/axe) or IDB. Pass `device` to target a specific simulator/emulator when multiple are available — call list_devices for the inventory.

Input parameters:

- `burst` (boolean): Capture rapid sequential frames after the swipe to detect transient feedback (overscroll bounce, fling-then-snap-back) even when the final state is unchanged. Frames are stored in the image buffer; u…
- `delta` (number): iOS only — touch step size between events (driver-dependent default). Ignored on Android.
- `device` (string): Target device. Accepts (a) an iOS simulator UDID, (b) an Android adb serial like 'emulator-5554', (c) the iOS simulator or Android emulator/device name (substring match), or (d) a connected RN app's…
- `direction` (string): Shorthand for a centered scroll gesture (content-scroll semantics): "up" reveals content below (finger moves bottom→top), "down" reveals content above, "left"/"right" page horizontally. A bare swipe(…
- `distance` (number): Travel length in screenshot pixels for the direction shorthand. Default: 33% of the relevant screen axis.
- `durationMs` (number): Swipe duration in milliseconds (default: 300 on Android; iOS uses driver default if omitted)
- `endX` (number): Ending X coordinate in screenshot pixels (explicit-coordinate mode)
- `endY` (number): Ending Y coordinate in screenshot pixels (explicit-coordinate mode)
- `screenshot` (boolean): Return the post-swipe image bytes in the response. Default true. Set to false to drop the PNG bytes — verification still runs (set verify=false to skip that too).
- `startX` (number): Starting X coordinate in screenshot pixels (explicit-coordinate mode)
- `startY` (number): Starting Y coordinate in screenshot pixels (explicit-coordinate mode)
- `verify` (boolean): Compare before/after screenshots to detect whether the swipe produced a visual change. Set false to skip. When skipped, the response contains `verification: { skipped: true, skippedReason }` so calle…

### `android_input_text` (~340 tokens)

Type text on an Android device/emulator.
[PLATFORM FALLBACK — prefer `input_text` — it targets, focuses, writes and verifies in one call unless you specifically need native-only behavior] The text will be input at the current focus point (tap an input field first).
PURPOSE: Send keystrokes to whichever input currently has focus on Android — the tool does NOT focus a field itself.
WHEN TO USE: Only after an input is already focused, or when `tap(text=...)` on the input didn't take focus for some reason.
PREREQUISITE: A TextInput must already have focus. Tap the field first (e.g. tap({ testID: 'search' })) — `android_input_text` does NOT focus a field itself; replace:true also requires React focus.
REPLACE MODE: pass replace:true to clear the focused field first (via React onChangeText so controlled state stays consistent), then type the new value. Use for pre-filled fields where appending would corrupt the value.

Input parameters:

- `device` (string): Optional RN device name (substring match) — needed by replace:true when multiple RN apps are connected, to disambiguate which device's focused input to clear. Single-device sessions can omit.
- `deviceId` (string): Android target: adb serial, emulator name, or RN device substring. Omit for first.
- `replace` (boolean): If true, clear the focused TextInput via React onChangeText before typing. Use to set a pre-filled field to an exact value without concatenation. Requires Bridgeless/Fabric.
- `text` (string, required): Text to type

### `android_key_event` (~268 tokens)

Send a key event to an Android device/emulator.
[PLATFORM-SPECIFIC — no cross-platform equivalent; use when sending Android key events (BACK, HOME, MENU, etc.)] Common keys: HOME, BACK, CALL, END_CALL, VOLUME_UP, VOLUME_DOWN, POWER, CAMERA, CLEAR, TAB, ENTER, DEL, MENU, SEARCH, MEDIA_PLAY_PAUSE, MEDIA_STOP, MEDIA_NEXT, MEDIA_PREVIOUS, MOVE_HOME, MOVE_END, APP_SWITCH, ESCAPE
PURPOSE: Dispatch Android system keys (BACK, HOME, MENU, ENTER, DEL, etc.) that aren't reachable via on-screen tap.
WHEN TO USE: Navigate back from a screen, submit a form with ENTER, dismiss the keyboard, or press hardware-style keys during a flow.

Input parameters:

- `deviceId` (string): Android target: adb serial, emulator name, or RN device substring. Omit for first.
- `key` (string, required): Key name (HOME, BACK, CALL, END_CALL, VOLUME_UP, VOLUME_DOWN, POWER, CAMERA, CLEAR, TAB, ENTER, DEL, MENU, SEARCH, MEDIA_PLAY_PAUSE, MEDIA_STOP, MEDIA_NEXT, MEDIA_PREVIOUS, MOVE_HOME, MOVE_END, APP_S…

### `ios_button` (~207 tokens)

Press a hardware button on an iOS simulator.
[PLATFORM-SPECIFIC — no cross-platform equivalent; use when pressing iOS hardware buttons (HOME, LOCK, SIRI, APPLE_PAY)] Requires an iOS UI driver: AXe (recommended: brew install cameroncooke/axe/axe) or IDB (brew install idb-companion).
PURPOSE: Trigger iOS hardware buttons (HOME, LOCK, SIDE_BUTTON, SIRI, APPLE_PAY) that aren't reachable via on-screen tap.
WHEN TO USE: Send the app to background (HOME), lock the simulator (LOCK), or exercise Siri/Apple Pay flows.

Input parameters:

- `button` (string, required): Hardware button to press: HOME, LOCK, SIDE_BUTTON, SIRI, or APPLE_PAY
- `duration` (number): Optional button press duration in seconds
- `udid` (string): iOS target: UDID, simulator name, or RN device substring. Omit for booted.

### `dismiss_keyboard` (~166 tokens)

Blur the currently focused TextInput, dismissing the on-screen keyboard.
PURPOSE: Close the keyboard when it's blocking content beneath the input, or move focus off an input before a tap that would otherwise be intercepted.
WHEN TO USE: After typing into a field and before tapping a button that is hidden by the keyboard. Or to verify a 'tap outside dismisses' UX is wired up.
PREREQUISITE: A TextInput must already have React focus. Tap the field first (e.g. tap({ testID: 'search' })).
LIMITATIONS: Requires Bridgeless/Fabric (RN new architecture). Returns 'no focused TextInput' if nothing is focused.

Input parameters:

- `device` (string): Optional device name (substring match). Uses default device if not specified.

### `ios_input_text` (~411 tokens)

Type text on an iOS simulator.
[PLATFORM FALLBACK — prefer `input_text` — it targets, focuses, writes and verifies in one call unless you specifically need native-only behavior] The text is typed into whichever field currently has focus (tap an input first). Mirrors `android_input_text` so cross-platform agents can use `<platform>_input_text` without branching on the iOS driver shell-out.
PURPOSE: Send keystrokes to the focused field on an iOS simulator via the active UI driver (AXe — preferred — or IDB).
WHEN TO USE: Only after an input is already focused, or when `tap(testID=...)` on the input didn't take focus for some reason. Use the testID-first flow whenever possible — it's faster and survives UI repositioning.
REPLACE MODE: pass replace:true to clear the focused field first (via React onChangeText so controlled state stays consistent), then type the new value. Use for pre-filled fields where appending would corrupt the value.
LIMITATIONS: AXe types via the US-keyboard HID — non-ASCII characters (Cyrillic, CJK, Arabic) may not transmit correctly. If the active driver is AXe and the text contains non-ASCII chars, prefer pasting via the simulator pasteboard or setting IOS_DRIVER=idb.

Input parameters:

- `device` (string): Optional RN device name (substring match) — needed by replace:true when multiple RN apps are connected, to disambiguate which device's focused input to clear. Single-device sessions can omit.
- `replace` (boolean): If true, clear the focused TextInput via React onChangeText before typing. Use to set a pre-filled field to an exact value without concatenation. Requires Bridgeless/Fabric.
- `text` (string, required): Text to type into the currently focused field.
- `udid` (string): iOS target: UDID, simulator name, or RN device substring. Omit for booted.

### `input_text` (~531 tokens)

Write text into a React Native TextInput and verify it landed.
[PRIMARY INTERACTION TOOL — works on iOS and Android; prefer over ios_*/android_* siblings]
PURPOSE: Set a field's text and confirm, by reading the value back, that the field holds exactly what you sent.
WHEN TO USE: Any text entry in a React Native app. Pass testID (or component/textMatch) and this tool focuses the field itself — no separate tap needed.
WORKFLOW: get_screen_state to see the fields -> input_text({ testID, text }) -> read `verified`.
VERIFICATION: the write is read back and compared EXACTLY. A mismatch retries once, then fails with `sent` vs `landed`. A success means the field really holds your string.
AMBIGUITY: if the target matches several inputs the tool refuses and returns a numbered candidate list (label, placeholder, value, testID) — pick one with `index` rather than guessing. Forms routinely share a placeholder across every field.
KEYBOARD: after the text is in, the software keyboard is raised on a best-effort basis so keyboard-up layout can be inspected. Failure there is reported, never fatal.
LIMITATIONS: fields with no onChangeText (uncontrolled, or non-RN) fall back to the platform driver, which is US-keyboard only — non-ASCII fails there and the result may be verified:false.
GOOD: input_text({ testID: "new-topic-title", text: "Q3 budget", replace: true })
BAD: input_text({ text: "..." }) with nothing focused — pass a target instead.

Input parameters:

- `component` (string): Target by React component name (case-insensitive substring), e.g. 'FormInput'. Use when there is no testID.
- `device` (string): RN device name (substring match). Omit when one app is connected; see get_apps.
- `index` (number): Zero-based choice when the target matches several inputs. The response's candidate list gives the indexes.
- `replace` (boolean): Replace the field's contents instead of appending. Default false (append).
- `testID` (string): Target the input with this testID. Most reliable — the tool focuses it itself, no prior tap needed.
- `text` (string, required): The text to write into the field.
- `textMatch` (string): Target by the field's visible label, placeholder, or current value (case-insensitive substring). NOTE: this picks WHICH field to write to; `text` is what gets written.

### `get_screen_layout` (~408 tokens)

Get a screen map showing visible components as an indented tree with actual screen positions. Uses measureInWindow for real coordinates and filters out off-screen components. Returns meaningful component names with text content and frame data (x,y width x height). Coordinates are delivered-screenshot pixels — the same space screenshots, get_screen_state and tap() use, so pass them through unchanged. Use extended=true to include layout styles (padding, margin, flex, backgroundColor, etc.).
[PRIMARY INTERACTION TOOL — works on iOS and Android; prefer over ios_*/android_* siblings]
PURPOSE: Quickest textual map of what is actually on screen right now — component names, positions, and text — so you can plan taps and inspections without guessing.
WHEN TO USE: First step whenever the user asks "what's on screen", "why is X covering Y", or before tapping a visually ambiguous element.
WORKFLOW: get_screen_layout -> find_components(pattern="...") or inspect_component(componentName="...") -> tap(testID=...) -> get_screen_layout again to confirm.
LIMITATIONS: pass coordinates straight to tap(), which handles conversion — never multiply by devicePixelRatio yourself.
GOOD: get_screen_layout({ extended: true })
BAD: get_screen_layout({ summary: true }) when you actually need to pick a specific element — summary hides the tree.
SOURCE: file:line for an element? inspect_at_point(x, y).

Input parameters:

- `device` (string): RN device name (substring). Omit for default; see get_apps.
- `extended` (boolean): Include layout styles (padding, margin, flex, backgroundColor, borderRadius, etc.) for each component. Default: false for compact output.
- `summary` (boolean): Return only component counts by name instead of full tree (default: false)
- `timeoutMs` (number): Per-call timeout in milliseconds. Default: 5000; bumped to 15000 when extended=true. Hard cap: 120000.

### `get_component_tree` (~482 tokens)

Get the React component tree from the running app — the fiber hierarchy including providers, navigation wrappers, and internal components. For a screen overview with positions and text, use get_screen_layout instead. Returns compact names-only structure by default; pass structureOnly=false for the full detailed tree.
PURPOSE: Expose the entire fiber tree — including providers, navigators, and off-screen subtrees — when get_screen_layout's visible-only view isn't enough.
WHEN TO USE: Debugging context propagation, navigation wrappers, hidden modals, or when you need to understand the full React architecture.
WORKFLOW: get_component_tree() for overview -> find_components for targeted lookup -> inspect_component for props/state.
LIMITATIONS: The detailed tree (structureOnly=false) is very large and routinely exceeds response-size limits on real apps — reach for inspect_component on a specific node instead. Ignores non-React native views. Minified builds return display names that may be opaque.
GOOD: get_component_tree()
BAD: get_component_tree({ structureOnly: false, includeProps: true, includeStyles: true }) on a large app — prefer inspect_component for specific nodes.

Input parameters:

- `device` (string): RN device name (substring). Omit for default; see get_apps.
- `format` (string): Output format: 'json' or 'compact' (default, indented tree — roughly 6x smaller than json). Ignored if structureOnly=true.
- `hideInternals` (boolean): Hide internal RN components (RCTView, RNS*, Animated, etc.) for cleaner output (default: true)
- `includeProps` (boolean): Include component props (excluding children and style). Ignored if structureOnly=true.
- `includeStyles` (boolean): Include layout styles (padding, margin, flex, etc.). Ignored if structureOnly=true.
- `maxDepth` (number): Maximum tree depth (default: 5000)
- `structureOnly` (boolean): Return ultra-compact structure with just component names (no props, styles, or paths). Default true — the detailed tree averages tens of thousands of tokens and is rarely what you want. Set false onl…
- `timeoutMs` (number): Per-call timeout in milliseconds. Default: 5000. Hard cap: 120000.

### `get_screen_state` (~487 tokens)

Screenshot-free snapshot of the current screen: active route + params, blocking overlays (sheets, modals, alerts), and every on-screen element merged top-to-bottom within reachability groups. Call after any tap or navigation to orient before the next action. Each line carries an (x, y) center + frame bounds (so anything is a tap(x, y) target), typed by a leading marker: 🔘 pressable (with component JSX tag, label, testID, onPress hint), 📝 text, 🖼 image (with src/alt). Elements covered by an open overlay are grouped under 🚫 Blocked — visible for context, but taps will NOT reach them until the overlay closes.

WHEN TO USE: After every tap/swipe that may navigate, and to read screen content (prices, labels, which image loaded) without a screenshot+OCR round-trip.
COORDINATES: delivered-screenshot pixels — the same space as ios_screenshot/android_screenshot, get_screen_layout, inspect_at_point, measure and tap(). Pass them through unchanged; never scale by devicePixelRatio yourself.
LIMITATIONS: route is null without React Navigation / Expo Router. Requires a live Metro connection.
HISTORY: includeHistory=true appends the route trail (dwell + origin per screen).
SOURCE: this lists what is on screen, not where it lives in code — for the file:line that renders an element, call inspect_at_point(x, y).
SEE ALSO: get_screen_layout for the full hierarchical component tree (deep inspection) — this gives a flat, tap-ready content list instead.

Input parameters:

- `device` (string): RN device name (substring). Omit for default; see get_apps.
- `fullText` (boolean): Emit each text node's full string instead of the 80-char truncation. Default false.
- `includeHistory` (boolean): Append the route trail — which screens the app has been on, most recent first, with dwell time and the route each was entered from. Recorded from connection time; an app restart shows an epoch divide…
- `pressablesOnly` (boolean): Return only route + overlays + pressables (the lean orientation snapshot), omitting on-screen text and images. Default false.

### `inspect_component` (~528 tokens)

Inspect a specific React component by name. **DRILL-DOWN TOOL**: Use after get_screen_layout or find_components to identify which component to inspect. Returns props, style, state (hooks), and optionally children tree. Use childrenDepth to control how deep nested children go.
[PRIMARY INTERACTION TOOL — works on iOS and Android; prefer over ios_*/android_* siblings]
PURPOSE: Reveal a mounted component's live props, hook state, and (optionally) child subtree so you can reason about why it renders the way it does.
WHEN TO USE: User asks "why is this button disabled", "what props does X receive", or you need to confirm state changed after a tap.
WORKFLOW: get_screen_layout or find_components -> inspect_component(componentName="Foo") -> tap or execute_in_app to change state -> inspect_component again.
LIMITATIONS: Requires the component to be currently mounted in the fiber tree. Name matching is exact; use find_components for fuzzy/regex lookup.
GOOD: inspect_component({ componentName: "SneakerCard", index: 0 })
BAD: inspect_component({ componentName: "Card" }) when many Card instances exist — pass index or narrow via find_components.

Input parameters:

- `childrenDepth` (number): How many levels deep to show children (default: 1 = direct children only, 2+ = nested tree)
- `componentName` (string, required): Name of the component to inspect (e.g., 'Button', 'HomeScreen', 'FlatList')
- `device` (string): RN device name (substring). Omit for default; see get_apps.
- `includeChildren` (boolean): Include children component tree
- `includeState` (boolean): Include component state/hooks (default: true)
- `includeStyle` (boolean): Include flattened style on each child entry (only with includeChildren). Use when debugging 'why isn't X style applying' or cascade-like inheritance on nested elements (e.g., textAlign on an inner Te…
- `index` (number): If multiple instances exist, which one to inspect (0-based index, default: 0)
- `shortPath` (boolean): Show only last 3 path segments (default: true)
- `simplifyHooks` (boolean): Simplify hooks output by hiding effects and reducing depth (default: true)
- `timeoutMs` (number): Per-call timeout in milliseconds. Default: 5000. Hard cap: 120000.

### `find_components` (~505 tokens)

Find components matching a name pattern. **TARGETED SEARCH**: Use after get_screen_layout or get_component_tree(structureOnly=true) to find specific components by pattern. Use includeLayout=true to get padding/margin/flex styles.
[PRIMARY INTERACTION TOOL — works on iOS and Android; prefer over ios_*/android_* siblings]
PURPOSE: Fast regex search over the entire fiber tree — including off-screen and wrapper components — to locate every instance of a component by name.
WHEN TO USE: You know roughly what the component is called (e.g., "Button", "Screen$") but not where it lives, or you need counts/paths before drilling in with inspect_component.
WORKFLOW: get_screen_layout (orient) -> find_components(pattern="...") -> inspect_component(componentName="...", index=N).
LIMITATIONS: Matches the React display name only; minified builds may return opaque names. Large result sets — use maxResults or a tighter pattern.
GOOD: find_components({ pattern: "Button" }); find_components({ pattern: "Screen$" })
BAD: find_components({ pattern: ".*" }) — floods the response; narrow the regex.
SOURCE: searching by name to find a file? If you can point at it on screen, inspect_at_point(x, y) returns the file and line directly.

Input parameters:

- `device` (string): RN device name (substring). Omit for default; see get_apps.
- `format` (string): Output format: 'json' or 'compact' (default, pipe-delimited rows — roughly 4.5x smaller than json)
- `includeLayout` (boolean): Include layout styles (padding, margin, flex) for each matched component
- `maxResults` (number): Maximum number of results to return (default: 20)
- `pattern` (string, required): Regex pattern to match component names (case-insensitive). Examples: 'Button', 'Screen$', 'List.*Item'
- `shortPath` (boolean): Show only last 3 path segments (default: true)
- `summary` (boolean): Return only component counts by name instead of full list (default: false)
- `timeoutMs` (number): Per-call timeout in milliseconds. Default: 5000. Hard cap: 120000.
- `visibleOnly` (boolean): Skip components inside hidden/inactive navigation scenes (unfocused Drawer/Tab destinations, inactive screens). Default false = search the entire fiber tree.

### `inspect_at_point` (~432 tokens)

Inspect layout AND props at (x, y). Returns FRAME PER ANCESTOR (position/size in delivered-screenshot pixels, the same space as screenshots/get_screen_state/tap, for every ancestor that hit-tested the point) + the innermost component's PROPS (handlers as [Function], refs, custom props like onPress/data/testID). Pure JS hit-test via fiber + measureInWindow — no overlay toggled, zero visual side effect. Works on Paper and Fabric.
PURPOSE: Layout/props diagnosis — "where is each ancestor positioned, and what props does the touched component expose?"
WHEN TO USE: A button is clipped, hit area is wrong, animated frame is unexpected — or you need handler/ref/non-style props. Also preferred for tight loops (no overlay flicker).
WORKFLOW: screenshot or get_screen_state → take the coordinate as-is → inspect_at_point(x, y).
LIMITATIONS: Style is the node's own style object, not the merged cascade. `frame` is the element's own box; `hitFrame` (when present) is the innermost node actually under the point.
SOURCE: also returns `source: {file, line, column}` for the component at the point, plus the owner chain as `Source ancestors` (set source=false to skip in tight loops).

Input parameters:

- `device` (string): RN device name (substring). Omit for default; see get_apps.
- `includeFrame` (boolean): Include position/dimensions (frame) in the output (default: true)
- `includeProps` (boolean): Include component props in the output (default: true)
- `source` (boolean): Resolve the component's source file and line via Metro symbolication. Default true. Set false to skip in tight loops.
- `x` (number, required): X coordinate in screen space — take it straight from a screenshot, get_screen_state or get_screen_layout. No conversion.
- `y` (number, required): Y coordinate in screen space — take it straight from a screenshot, get_screen_state or get_screen_layout. No conversion.

### `measure` (~374 tokens)

Get on-screen geometry {x, y, width, height} for a named React component instance. Calls measureInWindow on the matched fiber (or its nearest host descendant for composite components). Coordinates are delivered-screenshot pixels, the same space as screenshots, get_screen_layout, get_screen_state, inspect_at_point and tap().
PURPOSE: One-shot, name-based component measurement — avoids hand-rolling fiber walks and Promise-wrapping measureInWindow callbacks in execute_in_app.
WHEN TO USE: You already know the component's display name (from get_screen_layout or find_components) and just need its current bounds — e.g. to verify a layout change, compute a tap target, or compare against design specs.
WORKFLOW: find_components(pattern="...") -> measure(componentName="...", index=N) -> tap(x, y) at the center, or inspect_at_point at the center to verify identity.
LIMITATIONS: Returns post-layout on-screen geometry only — for static style use find_components({ includeLayout: true }). For point-based lookup use inspect_at_point. Off-screen fibers may return zeros; that's the truth, not an error. Composites with multiple host descendants return the first host descendant's bounds.
GOOD: measure({ componentName: "SneakerCard", index: 0 })
BAD: measure({ componentName: "View" }) — too generic; narrow with find_components first.
SEE ALSO: inspect_at_point for point-based variant; find_components({ includeLayout: true }) for static style.

Input parameters:

- `componentName` (string, required): Exact React display name to match (same matcher as inspect_component).
- `device` (string): RN device name (substring). Omit for default; see get_apps.
- `index` (number): 0-based index when multiple instances match (default: 0).

## Diagnostics

Captured diagnostic sections: Provenance, Vulnerabilities, Dependencies. The full working is on the page: https://verifymcp.io/servers/com-execbro-execbro/execbro#diagnostics

## Score history

- 2026-08-03: 67
- 2026-08-02: 69
- 2026-08-01: 71

## Links

- npm package: https://www.npmjs.com/package/execbro
- Socket report: https://socket.dev/npm/package/execbro
- Repository: https://github.com/igorzheludkov/execbro
- Website: https://execbro.com/
- Changelog RSS feed: https://verifymcp.io/servers/com-execbro-execbro/execbro/changelog.xml
- Changelog JSON feed: https://verifymcp.io/servers/com-execbro-execbro/execbro/changelog.json
- HTML version of this page: https://verifymcp.io/servers/com-execbro-execbro/execbro
