ExecBro
NPM · EXECBRO · SCANNED SEP 21
Gives AI agents eyes and hands into running React Native apps: logs, REPL, tap, screenshots
Available components
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. How we score → Why this is hard to score →
Supply Chain Security99
- No malware found by supply-chain analysis.Pass
- No known CVEs affecting this package version or its production dependencies.Pass
- No install/post-install scripts declared.Pass
- 32 of 143 dependencies flagged as unhealthy. View diagnostics → Partial
Provenance & Transparency97
- Source repository is publicly reachable at the declared URL. View diagnostics → Pass
- Cryptographically verified build provenance (signed, bound to igorzheludkov/execbro). View diagnostics → Pass
- Clear OSI-approved license (MIT).Pass
- Actively maintained (last published 1 days ago).Pass
- Disclosure check failed: no security disclosure policy was found in the source repository. See how to fix → Fail
Schema Quality & AI Usability63
- AI-judged instruction clarity (excellent).Pass
- Context-footprint check failed: tool/resource definitions use about 22380 tokens (~360/item across 62 items; 62 tools + 0 resources), over budget; trim descriptions and params. See how to fix → Fail
- Usage-examples check failed: none of the tools include examples. See how to fix → Fail
Stability & Change Management98
- Stability check failed: the tool surface changed between 2.9.2 and 2.12.3: 1 tool removals, 0 breaking changes, 3 additions. See how to fix → Fail
Tool Coverage100
- 100% of tools have a non-trivial description (not blank, and not just the tool's name).Pass
- 100% of tool parameters carry a description.Pass
Tool Safety75
- No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.Pass
- 0 of 5 tool(s) whose name or description implies an irreversible operation declare an MCP destructiveHint annotation; "delete_account" implies "delete" and declares no destructiveHint at all, which the MCP spec reads as destructive by default. See how to fix → Fail
- An AI judge read all 63 captured unit(s) of tool text and found none that tries to manipulate the model reading it.Pass
Capabilities100
- Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.Pass
How do I install the ExecBro MCP server?
ExecBro runs locally as an npm package, launched with npx -y execbro. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.
npm · execbro
claude mcp add com-execbro-execbro -- npx -y execbro
{
"mcpServers": {
"com-execbro-execbro": {
"command": "npx",
"args": [
"-y",
"execbro"
]
}
}
} {
"servers": {
"com-execbro-execbro": {
"command": "npx",
"args": [
"-y",
"execbro"
]
}
}
} codex mcp add com-execbro-execbro -- npx -y execbro
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"com-execbro-execbro": {
"type": "local",
"command": [
"npx",
"-y",
"execbro"
],
"enabled": true
}
}
} openclaw mcp add com-execbro-execbro --command npx --arg -y --arg execbro
mcp_servers:
com-execbro-execbro:
command: "npx"
args: ["-y", "execbro"] {
"McpServers": {
"com-execbro-execbro": {
"Transport": "stdio",
"Command": "npx",
"Arguments": [
"-y",
"execbro"
]
}
}
} assistant mcp add com-execbro-execbro -t stdio -c npx -a -y execbro
{
"mcpServers": {
"com-execbro-execbro": {
"command": "npx",
"args": [
"-y",
"execbro"
]
}
}
} Every change we have recorded for this component, newest first. Security-relevant changes are always shown. ▲ marks a change for the better, ▼ a change for the worse; unmarked changes are neutral.
- 21 Sept 26 +1
No change was recorded against any check on this day. Stability & Change Management went from 95 to 98.
- 19 Sept 26 +1
- Stability: fail → unverified ▼ security
- Tool safety: pass → unverified ▼ security
- Capabilities: pass → unverified ▼ functional
- Tool coverage: 100 → unverified ▼ functional
- Package version: 2.12.2 → 2.12.3 functional
- 18 Sept 26 0
- Package version: 2.9.2 → 2.12.2 functional
- 17 Sept 26 −2
No change was recorded against any check on this day. Stability & Change Management went from 98 to 85.
- 15 Sept 26 +1
No change was recorded against any check on this day. Stability & Change Management went from 95 to 98.
- 14 Sept 26 0
- Malware scan: pass → unverified ▼ security
- Stability: fail → unverified ▼ security
- Tool safety: pass → unverified ▼ security
- Capabilities: pass → unverified ▼ functional
- Tool coverage: 100 → unverified ▼ functional
- Package version: 2.12.0 → 2.12.2 functional
- Package version: 2.12.0 → 2.12.1 functional
- 12 Sept 26 +15
- Malware scan: unverified → pass ▲ security
- 11 Sept 26 +1
No change was recorded against any check on this day. Stability & Change Management went from 88 to 92.
Diagnostic detail from the automated scan of this channel: what the scanner observed at each step, so you can see exactly where a check passed or failed. It is informational only and never changes the trust score.
Captured 21 Sept 2026 · Analysed npm/execbro@2.12.3
Provenance Verified
A signed build attestation was found and verified, binding this exact artifact to the source repository it claims to come from.
| Result | Verified |
|---|---|
| Ecosystem | npm |
| Reason | Verified |
| Discovered via | Registry attestation endpoint |
| Source repo | igorzheludkov/execbro |
| Certificate issuer | https://token.actions.githubusercontent.com |
| Certificate SAN | https://github.com/igorzheludkov/execbro/.github/workflows/publish.yml@refs/tags/v2.12.3 |
| Rekor log index | 2892540503 |
| Predicate type | https://slsa.dev/provenance/v1 |
| Subject digest | sha512:f2a30101f41fcc8bd617e8f90fb7807165e2dcee67fd5bf96e0a5afb81718dad74a1555c3c50987c8c771c39dc66ee0fa63aec53de4e9ff4690cb807b |
Background: How many MCP packages publish verified provenance →
Dependencies 143 packages
| Packages resolved | 143 |
|---|---|
| Stale | 32 |
| Tree resolution | Complete |
Background: SBOMs and build attestations, explained →
The tools this component advertises to a client, with an estimated token cost for each. Expand a tool to see its parameters and schema. The per-tool counts are indicative and are not scored directly; the schema's total context footprint is one signal in Schema Quality & AI Usability. A tool's description is untrusted text the model reads on every call, which is what makes this list a security surface and not just an inventory: how tool poisoning works →
activate_license ~52
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.
| Name | Type | Req | Description |
|---|---|---|---|
| token | string | yes | Activation token from your dashboard |
No output schema declared.
No examples provided.
android_key_event ~268
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.
| Name | Type | Req | Description |
|---|---|---|---|
| deviceId | string | – | Android target: adb serial, emulator name, or RN device substring. Omit for first. |
| key | string | yes | 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… |
No output schema declared.
No examples provided.
android_launch_app ~167
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.
| Name | Type | Req | Description |
|---|---|---|---|
| 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 | yes | Package name of the app (e.g., com.example.myapp) |
No output schema declared.
No examples provided.
android_list_packages ~131
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.
| Name | Type | Req | Description |
|---|---|---|---|
| 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) |
No output schema declared.
No examples provided.
android_long_press ~198
Long press at specific coordinates on an Android device/emulator screen [PLATFORM FALLBACK — prefer `tap({ x, y, duration })` — it long-presses on both platforms and can resolve the target by testID/text/component 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: Android-only coordinate holds with no React Native connection. Anything reachable through RN should use `tap({ duration })`, which also reports whether the element has an onLongPress handler.
| Name | Type | Req | Description |
|---|---|---|---|
| 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 | yes | X coordinate in pixels |
| y | number | yes | Y coordinate in pixels |
No output schema declared.
No examples provided.
android_screenshot ~390
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).
| Name | Type | Req | Description |
|---|---|---|---|
| device | string | – | Alias for `deviceId` — same accepted values. Provided for consistency with tap/get_screen_state/ios_screenshot, which all accept `device`. If both are given, `deviceId` wins. |
| 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. |
No output schema declared.
No examples provided.
app_request ~517
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. 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. AUTH RESOLUTION (auth="auto", in order): explicit Authorization header -> redux (state.user.accessToken, state.auth.accessToken, state.auth.token) -> the Authorization header of the app's last captured request. That last step is source-agnostic, so it covers tokens kept outside redux — keychain, secure storage, an Apollo link — as long as the app has already made one authenticated call and the SDK captured it. Cookie auth needs none of this: the native cookie jar attaches cookies to any in-app request. LIMITATIONS: needs a connected app. Pass an explicit Authorization only when every step above misses — it puts the credential in the transcript. GOES THROUGH THE APP: same TLS trust, proxy, cookie jar and credentials, so an active network_mock rule intercepts it too. For a clean request from your own machine — to tell a server bug from a client one — use http_request. 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.
| Name | Type | Req | Description |
|---|---|---|---|
| auth | string | – | "auto" (default) resolves a bearer token in-app: redux, then the last captured request's Authorization header. "none" sends no Authorization header — the right choice for cookie-authenticated APIs. |
| body | – | – | Request body. An object is JSON-serialised for you; a string is sent verbatim (already-encoded JSON, urlencoded, raw). 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 | yes | HTTP method. |
| url | string | yes | Absolute URL to request. |
No output schema declared.
No examples provided.
clear_logs ~142
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.
| Name | Type | Req | Description |
|---|---|---|---|
| device | string | – | RN device name (substring). Omit for all devices; see get_apps. |
No output schema declared.
No examples provided.
clear_network ~152
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.
| Name | Type | Req | Description |
|---|---|---|---|
| device | string | – | RN device name (substring). Omit for all devices; see get_apps. |
No output schema declared.
No examples provided.
connect_metro ~142
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.
| Name | Type | Req | Description |
|---|---|---|---|
| port | number | – | Metro server port (default: 8081) |
No output schema declared.
No examples provided.
delete_account ~65
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.
| Name | Type | Req | Description |
|---|---|---|---|
| confirm | string | – | Must be exactly 'DELETE' to confirm account deletion |
No output schema declared.
No examples provided.
disconnect_metro ~241
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.
| Name | Type | Req | Description |
|---|---|---|---|
| device | string | – | RN device name (substring). Omit for all devices; see get_apps. |
No output schema declared.
No examples provided.
dismiss_keyboard ~185
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). With nothing focused it reports the keyboard already down; if the keyboard is up anyway, a native field owns it and only a platform dismiss will close it.
| Name | Type | Req | Description |
|---|---|---|---|
| device | string | – | Optional device name (substring match). Uses default device if not specified. |
No output schema declared.
No examples provided.
ensure_connection ~240
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.
| Name | Type | Req | Description |
|---|---|---|---|
| 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) |
No output schema declared.
No examples provided.
execute_in_app ~775
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.
| Name | Type | Req | Description |
|---|---|---|---|
| awaitPromise | boolean | – | Whether to await promises (default: true) |
| code | string | – | Alias for `expression`. |
| 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 | – | REQUIRED unless `collect` is used. JavaScript expression to execute. Must be valid Hermes syntax — no require(), no `await`/`async` (use `Promise.resolve(foo()).then(function(r){ return r; })`), no u… |
| 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" —… |
| script | string | – | Alias for `expression`. |
| 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+. |
| waitMs | number | – | collect only: block up to this long (ms, capped at 120000) waiting for the handle to settle, instead of returning 'Still pending' immediately. Default 0 — one look, no wait. |
No output schema declared.
No examples provided.
find_components ~505
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.
| Name | Type | Req | Description |
|---|---|---|---|
| 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 | yes | 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. |
No output schema declared.
No examples provided.
get_apps ~172
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.
Input schema present but exposes no named parameters.
No output schema declared.
No examples provided.
get_bundle_errors ~386
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.
| Name | Type | Req | Description |
|---|---|---|---|
| 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. |
No output schema declared.
No examples provided.
get_bundle_status ~193
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.
Input schema present but exposes no named parameters.
No output schema declared.
No examples provided.
get_component_tree ~482
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.
| Name | Type | Req | Description |
|---|---|---|---|
| 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. |
No output schema declared.
No examples provided.
get_connection_status ~298
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.
| Name | Type | Req | Description |
|---|---|---|---|
| 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. |
No output schema declared.
No examples provided.
get_images ~350
Access the shared image buffer containing screenshots from all tools (ios_screenshot, android_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.
| Name | Type | Req | Description |
|---|---|---|---|
| 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 |
No output schema declared.
No examples provided.
get_license_status ~53
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.
Input schema present but exposes no named parameters.
No output schema declared.
No examples provided.
get_log_details ~241
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.
| Name | Type | Req | Description |
|---|---|---|---|
| id | string | yes | Event id from get_logs (e.g. "n7") |
| maxLength | number | – | Max characters of payload (default: 4000, 0 for unlimited) |
| verbose | boolean | – | Disable truncation entirely |
No output schema declared.
No examples provided.
get_logs ~692
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.
| Name | Type | Req | Description |
|---|---|---|---|
| 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), ordered debug < log < info < warn < error < fatal. Crashes and ANRs are always returned regardless. On iOS use 'log': os_log 'Default' — what a plai… |
| 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. |
No output schema declared.
No examples provided.
get_network_requests ~448
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.
| Name | Type | Req | Description |
|---|---|---|---|
| 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. |
No output schema declared.
No examples provided.
get_refresh_status ~351
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).
| Name | Type | Req | Description |
|---|---|---|---|
| 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. |
No output schema declared.
No examples provided.
get_request_details ~602
Get full details of one network request: headers, body, status, timing. PURPOSE: Drill into a single entry after get_network_requests or search_network returns a suspect id. WORKFLOW: get id -> read the shape -> get_request_details({ requestId, query }) for the subtree you need. SHAPE FIRST: a large JSON body returns as its structure — key paths kept, arrays and objects annotated with real sizes, leaves clipped. The first call already answers 'is there an errors array' and 'does this field exist'. QUERY: dot-path into the JSON body, returned in full. Supports a.b.c, [0], [-1], [*], ["key.with.dots"]. Targets the response body, or the request body when there is none. No match returns the shape plus what IS there — retry from that. SIDES: with a query, only the queried body is rendered (no headers, no other side) — include:"response"/"request"/"both" overrides. Credential headers are redacted to scheme + length; verbose:true prints them. LIMITATIONS: Bodies need the execbro-sdk; CDP-only targets have no response body. Non-JSON is clipped at maxBodyLength, not projected. GOOD: get_request_details({ requestId: "42", query: "data.approvals.single.basicInfo" }) | query: "errors[*].message" BAD: verbose:true to find one field — that is the 40KB dump this avoids. Or guessing requestIds.
| Name | Type | Req | Description |
|---|---|---|---|
| device | string | – | RN device name (substring). Omit for all devices; see get_apps. |
| include | string | – | Which side to render (headers + body). Defaults to the side `query` targets, or "both" when there is no query — so narrowing with query no longer re-dumps the request headers and the whole GraphQL qu… |
| maxBodyLength | number | – | Byte target for each rendered body (default: 2000, 0 for unlimited). JSON is bounded structurally, so this trades depth and array width for size rather than cutting the text off. |
| query | string | – | Dot-path into the JSON body, returned in full: "data.items[0].id", "errors[*].message", "data[\"weird.key\"]". A leading $. is accepted. Omit to get the shape of the whole body. A path that matches n… |
| requestId | string | yes | The request ID to get details for |
| verbose | boolean | – | Return bodies raw and unbounded. Does NOT reveal secrets — credential headers, tokens in bodies and tokens in URLs stay redacted regardless; that is deliberate and only EXECBRO_REDACT=off lifts it. P… |
No output schema declared.
No examples provided.
get_screen_layout ~408
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).
| Name | Type | Req | Description |
|---|---|---|---|
| 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. |
No output schema declared.
No examples provided.
get_screen_state ~583
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). PARAMS: route params listed by key; fullParams=true adds values. 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 component tree; this is a flat, tap-ready content list.
| Name | Type | Req | Description |
|---|---|---|---|
| device | string | – | RN device name (substring). Omit for default; see get_apps. |
| fullParams | boolean | – | Emit the route params' values. Default false — only the param key names are listed, because the full blob is usually hundreds of characters of ids and image URLs. |
| 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. Pressables include Switch/checkbox elements (onValueChange), each rendere… |
No output schema declared.
No examples provided.
get_usage_guide ~93
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.
| Name | Type | Req | Description |
|---|---|---|---|
| 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. |
No output schema declared.
No examples provided.
http_request ~488
Issue an HTTP request FROM THE HOST (Node), not through the app, carrying a credential you cannot read. PURPOSE: Isolate server behaviour from client behaviour. This is what curl was for, minus the part where the token and the whole response landed in the transcript. HOW IT DIFFERS FROM app_request: app_request runs inside the app, so it carries the app's TLS trust, proxy, native cookie jar and credentials, and an active network_mock intercepts it. http_request is a clean request from your machine. Pick app_request when the app's real conditions matter, when the session is cookie-authenticated, or when the backend enforces attestation (Firebase App Check cannot be satisfied from Node). Pick http_request to tell a server bug from a client one. CREDENTIALS: pass auth:{secret:"<origin or handle>"} — names come from list_secrets. The value is substituted here and never rendered, and is bound to the origin it was observed on: any other host is refused. Default placement is Authorization: Bearer; set header for a key header (X-API-Key) or scheme for another (Basic). READING THE RESULT: a 401 here where app_request succeeds is itself an answer — the backend is enforcing attestation. GOOD: http_request({ method: "GET", url: "https://api.acme.io/v1/me", auth: { secret: "api.acme.io" } }) BAD: pasting a credential into headers to cover a shape auth does not — that is the transcript leak the vault exists to prevent. Report the gap instead.
| Name | Type | Req | Description |
|---|---|---|---|
| auth | object | – | Credential to attach. Typed on purpose: the credential position is structural, never string interpolation, so it cannot be smuggled into an arbitrary field. |
| body | – | – | Request body. An object is JSON-serialised and sets Content-Type: application/json unless you override it; a string is sent verbatim. |
| headers | object | – | Extra request headers. One given here wins over auth when the names collide — but do not use that to pass a credential value, which is the transcript leak auth exists to prevent. |
| maxResultLength | number | – | Target size for the response body in characters (default 25000). |
| method | string | yes | HTTP method. |
| url | string | yes | Absolute http or https URL. |
No output schema declared.
No examples provided.
input_text ~613
Write text into a React Native TextInput, or the OS's currently focused field, 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 it holds exactly what you sent. WHEN TO USE: Any text entry in a React Native app. Pass testID (or component/textMatch) and this focuses the field itself — no separate tap needed. WORKFLOW: get_screen_state -> 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`. AMBIGUITY: several matching inputs -> the tool refuses and returns a numbered candidate list; pick one with `index`. NATIVE SCREENS: with no React fiber tree at all (system dialog, native onboarding, non-RN app) this falls back automatically to typing into whatever the OS reports as focused — tap it first. native:true forces that path, and ignores testID/component/textMatch: it cannot target, only type into what already has focus. LIMITATIONS: fields with no onChangeText fall back to the platform driver, which is US-keyboard only — non-ASCII fails there. The native path shares that limit. GOOD: input_text({ testID: "new-topic-title", text: "Q3 budget", replace: true }) GOOD: input_text({ text: "1234", native: true }) — a system PIN prompt, no RN screen behind it. BAD: input_text({ text: "..." }) with nothing focused — pass a target instead.
| Name | Type | Req | Description |
|---|---|---|---|
| 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. |
| native | boolean | – | Skip React targeting and type directly into whichever field the OS reports as focused, via the platform accessibility tree. For system dialogs and non-RN screens. Ignores testID/component/textMatch/i… |
| 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 | yes | 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. |
No output schema declared.
No examples provided.
inspect_at_point ~432
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).
| Name | Type | Req | Description |
|---|---|---|---|
| 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 | yes | X coordinate in screen space — take it straight from a screenshot, get_screen_state or get_screen_layout. No conversion. |
| y | number | yes | Y coordinate in screen space — take it straight from a screenshot, get_screen_state or get_screen_layout. No conversion. |
No output schema declared.
No examples provided.
inspect_component ~528
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.
| Name | Type | Req | Description |
|---|---|---|---|
| childrenDepth | number | – | How many levels deep to show children (default: 1 = direct children only, 2+ = nested tree) |
| componentName | string | yes | 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. |
No output schema declared.
No examples provided.
inspect_global ~480
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. WIDE OBJECTS: a global with hundreds of keys is bounded structurally rather than dumped; use query to pull one entry in full. GOOD: inspect_global({ objectName: "__APOLLO_CLIENT__" }) | inspect_global({ objectName: "__RN_AI_DEVTOOLS__.stores.redux", query: "cache" }) BAD: inspect_global({ objectName: "store.getState()" }) — call expressions aren't supported; use execute_in_app.
| Name | Type | Req | Description |
|---|---|---|---|
| device | string | – | RN device name (substring). Omit for default; see get_apps. |
| maxResultLength | number | – | Byte target for the rendered listing (default: 25000, 0 for unlimited). |
| objectName | string | yes | Identifier or dotted path of the global to inspect (e.g., '__APOLLO_CLIENT__', '__RN_AI_DEVTOOLS__.stores.redux', '__RN_AI_DEVTOOLS__.custom.mmkv') |
| query | string | – | Dot-path into the property listing, returned in full (e.g. "cache" or "stores.redux"). A path that matches nothing returns the listing plus what is actually there, not an error. |
No output schema declared.
No examples provided.
ios_boot_simulator ~107
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.
| Name | Type | Req | Description |
|---|---|---|---|
| udid | string | yes | UDID of the simulator to boot (from list_devices) |
No output schema declared.
No examples provided.
ios_button ~207
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.
| Name | Type | Req | Description |
|---|---|---|---|
| button | string | yes | 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. |
No output schema declared.
No examples provided.
ios_launch_app ~139
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.
| Name | Type | Req | Description |
|---|---|---|---|
| bundleId | string | yes | Bundle ID of the app (e.g., com.example.myapp) |
| udid | string | – | iOS target: UDID, simulator name, or RN device substring. Omit for booted. |
No output schema declared.
No examples provided.
ios_open_url ~249
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]
| Name | Type | Req | Description |
|---|---|---|---|
| udid | string | – | iOS target: UDID, simulator name, or RN device substring. Omit for booted. |
| url | string | yes | URL to open (e.g., https://example.com or myapp://path) |
No output schema declared.
No examples provided.
ios_screenshot ~451
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. PHYSICAL DEVICES: a USB-attached iPhone/iPad works too (pass its UDID or name from list_devices) — capture only, via pymobiledevice3. No pressables, no tap/swipe/input_text: iOS has no touch injection below 17. LIMITATIONS: Otherwise requires a booted iOS simulator (simctl). For 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.
| Name | Type | Req | Description |
|---|---|---|---|
| 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. |
No output schema declared.
No examples provided.
ios_terminate_app ~133
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.
| Name | Type | Req | Description |
|---|---|---|---|
| bundleId | string | yes | Bundle ID of the app to terminate |
| udid | string | – | iOS target: UDID, simulator name, or RN device substring. Omit for booted. |
No output schema declared.
No examples provided.
list_debug_globals ~327
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 init({ stores, navigation, custom }) from execbro-sdk (formerly react-native-ai-devtools-sdk) 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.
| Name | Type | Req | Description |
|---|---|---|---|
| device | string | – | RN device name (substring). Omit for default; see get_apps. |
No output schema declared.
No examples provided.
list_devices ~225
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).
| Name | Type | Req | Description |
|---|---|---|---|
| refresh | boolean | – | Force re-query of simctl/adb/emulator instead of returning cached results (5s TTL). |
No output schema declared.
No examples provided.
list_secrets ~171
List the credentials this session has captured, by handle. Values are never shown. PURPOSE: Find the name to pass to http_request({ auth: { secret } }), and see at a glance which entries have expired. WHAT YOU GET: handle, kind, the origin it was seen on, age, and JWT expiry where the token carries one. Nothing derived from the token's claims — a JWT's issuer and subject are self-asserted, so they are not reported. LIMITATIONS: memory only. A server restart empties the vault, and a handle from an older transcript resolves to nothing. GOOD: list_secrets() -> http_request({ url: "https://api.acme.io/v1/me", method: "GET", auth: { secret: "api.acme.io" } })
Input schema present but exposes no named parameters.
No output schema declared.
No examples provided.
logbox ~535
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.
| Name | Type | Req | Description |
|---|---|---|---|
| action | string | yes | 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 |
No output schema declared.
No examples provided.
measure ~374
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.
| Name | Type | Req | Description |
|---|---|---|---|
| componentName | string | yes | 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). |
No output schema declared.
No examples provided.
navigate ~375
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. DISCOVERY: navigate({ routeTable: true }) with no destination just LISTS the registered route names — call it first when you do not know them. GOOD: navigate({ to: "TarotNav" }); navigate({ action: "back" }); navigate({ routeTable: true }) BAD: navigate({ to: "/TarotNav" }) on React Navigation — that is a path, not a route name.
| Name | Type | Req | Description |
|---|---|---|---|
| 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. Pass it alone, with no `to`, to just list them. |
| to | string | – | Destination: a path for Expo Router, a route name for React Navigation. Required unless action is "back" or "reset". |
No output schema declared.
No examples provided.
network_condition ~243
Simulate offline or slow network for the running app. PURPOSE: Reach the offline / timeout branches through the app's real code. WHEN TO USE: 'what does this screen do with no network', 'is there a loading state', 'does the retry banner appear'. WORKFLOW: network_condition({mode:"offline"}) -> reproduce -> network_condition({mode:"normal"}). OFFLINE also patches NetInfo when installed, because many apps gate their offline UI on useNetInfo() rather than on a failed request. The result reports netInfo as patched / reads-patched-only / not-installed. Request failure works regardless. LIMITATIONS: JS-originated HTTP only. Does not change the device's real connectivity. Leaves network_mock rules untouched. GOOD: network_condition({mode:"slow", latencyMs:3000}) BAD: forgetting network_condition({mode:"normal"}) afterwards — it survives reload_app.
| Name | Type | Req | Description |
|---|---|---|---|
| device | string | – | RN device name (substring). Omit for all devices; see get_apps. |
| latencyMs | number | – | Delay per request in slow mode. |
| mode | string | yes | offline fails every request; slow delays them; normal clears. |
No output schema declared.
No examples provided.
network_mock ~542
Intercept the app's HTTP requests and replace or modify the response. PURPOSE: Drive the app down an error path through its REAL code — the request builder, the error branch, the retry, the toast. redux_dispatch writes the post-failure state directly and skips all of it. WHEN TO USE: 'what does this screen do on a 500', 'what if this field is null', 'does the retry work'. WORKFLOW: network_mock({action:"add", url:"/orders", status:500}) -> reproduce -> get_network_requests (rows show [MOCK m1]) -> network_mock({action:"clear"}). MODES: replace returns a canned response; tamper fetches the real one and mutates it (set/remove take dotted paths). MATCHING: url is a substring by default; wrap it in slashes for a regex ("/\\/orders\\/\\d+$/"). First matching rule wins, so add specific rules before broad ones. LIMITATIONS: JS-originated HTTP only — native-module traffic (native SDKs, <Image> loading) is not intercepted. Rules are per-device and survive reload_app; clear them when done. GOOD: network_mock({action:"add", url:"/orders", mode:"tamper", remove:["data.email"]}) BAD: leaving a rule active and then debugging why the app 'always fails' — check network_mock({action:"list"}) hit counts first.
| Name | Type | Req | Description |
|---|---|---|---|
| action | string | yes | What to do. |
| body | string | – | Response body (replace mode). |
| bodyReplace | string | – | tamper: replace the whole body. |
| delayMs | number | – | Delay before delivering. |
| device | string | – | RN device name (substring). Omit for all devices; see get_apps. |
| headers | object | – | Response headers (replace mode). |
| id | string | – | Rule id, for action="remove". |
| method | string | – | Restrict to one HTTP method. |
| mode | string | – | replace = canned response; tamper = mutate the real one. |
| networkError | string | – | Fail the request instead of responding. |
| remove | array | – | tamper: dotted paths to delete. |
| set | object | – | tamper: dotted path -> value, e.g. {"data.user.email": null}. |
| status | number | – | Response status. In tamper mode, overrides the real status. |
| times | number | – | Fire at most N times, then pass through. Use times:1 to test retry logic. |
| url | string | – | URL substring, or /regex/ when slash-wrapped. |
No output schema declared.
No examples provided.
What is the ExecBro MCP server?
ExecBro is an MCP server listed in the public MCP registry as com.execbro/execbro. Gives AI agents eyes and hands into running React Native apps: logs, REPL, tap, screenshots. This page covers its npm package (execbro).
Is the ExecBro MCP server safe to use?
ExecBro scores 91 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 21 September 2026. It declares no install or post-install scripts. Its build provenance is signed and verified. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.
What tools does the ExecBro MCP server expose?
ExecBro exposes 62 tools: activate_license, delete_account, get_usage_guide, get_license_status, send_feedback, and 57 more. Their descriptions and schemas cost roughly 21,970 tokens of context every time the server is loaded.
Is the ExecBro MCP server still maintained?
ExecBro is still listed as active in the MCP registry. We last reached this channel on 21 September 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.
What licence is the ExecBro MCP server under?
ExecBro declares the MIT licence, which is OSI-approved. That covers the source only, and says nothing about the cost of any service it calls.