unlikeotherai.github.io/AppReveal
Debug-only in-app MCP server for iOS, macOS, Android, Flutter, and React Native. Lets LLM agents discover, inspect, and control native apps over the local network -- like Playwright for native, but with direct access to app state, navigation, network traffic, DOM, and diagnostics.
Your App (debug build) External Agent
+-- AppReveal framework +-- mDNS browse for _appreveal._tcp
+-- MCP Server (Streamable HTTP) <---+-- MCP client (curl, SDK, Claude, etc.)
+-- mDNS advertisement +-- LLM orchestration
+-- Screen/element/state bridges
+-- WebView DOM bridge
- App calls
AppReveal.start()in a debug build - Framework starts an HTTP server on a dynamic port
- mDNS advertises the service as
_appreveal._tcpon the LAN - Agent discovers the service, connects, and calls MCP tools
AppReveal shares the same core MCP surface across platforms. macOS adds desktop-specific window and menu tools on top of the shared native and web view tools.
// Package.swift
.package(url: "https://github.com/UnlikeOtherAI/AppReveal.git", from: "0.8.0")#if DEBUG
AppReveal.start()
#endifSee iOS guide for full setup.
// Package.swift
.package(url: "https://github.com/UnlikeOtherAI/AppReveal.git", from: "0.8.0")#if DEBUG
AppReveal.start()
#endifSee macOS guide for full setup.
// build.gradle.kts
debugImplementation("com.appreveal:appreveal")
releaseImplementation("com.appreveal:appreveal-noop")if (BuildConfig.DEBUG) {
AppReveal.start(this)
}See Android guide for full setup.
// pubspec.yaml
dependencies:
appreveal:
path: Flutter/apprevealvoid main() {
WidgetsFlutterBinding.ensureInitialized();
AppReveal.start(); // no-ops in release builds
runApp(AppReveal.wrap(const MyApp()));
}
// In MaterialApp:
MaterialApp(
navigatorObservers: [AppReveal.navigatorObserver],
...
)See Flutter guide for full setup.
npm install react-native-appreveal
cd ios && pod installimport { AppReveal, AppRevealFetchInterceptor } from 'react-native-appreveal';
if (__DEV__) {
AppReveal.start();
AppRevealFetchInterceptor.install();
}See React Native guide for full setup.
| Tool | Description |
|---|---|
list_windows |
List visible app windows and their IDs |
get_screen |
Current screen identity, controller/activity chain, confidence score |
get_elements |
All visible interactive elements with id, type, frame, safe-area data, actions |
get_view_tree |
Full view hierarchy with class, frame, safe-area data, properties, accessibility info |
tap_element |
Tap by element identifier (buttons, cells, controls) |
tap_text |
Tap by visible text content (finds text, walks to tappable ancestor) |
tap_point |
Tap at screen coordinates |
type_text |
Type text into a field (by element ID or current responder) |
clear_text |
Clear a text field |
scroll |
Scroll a container (up/down/left/right) |
scroll_to_element |
Scroll until an element is visible |
screenshot |
Capture screen or element as base64 PNG/JPEG |
select_tab |
Switch tab bar tabs by index |
navigate_back |
Pop the navigation stack |
dismiss_modal |
Dismiss the topmost modal |
open_deeplink |
Open a URL in the app |
All native UI tools and all web view tools accept an optional window_id parameter from list_windows. If omitted, AppReveal targets the current key window.
| Tool | Description |
|---|---|
get_menu_bar |
Read the app menu bar hierarchy |
click_menu_item |
Invoke a menu item by title path |
focus_window |
Bring a specific window to the front and make it key |
| Tool | Description |
|---|---|
get_state |
App state snapshot (login, user, cart, etc.) |
get_navigation_stack |
Current route, nav stack, modal stack |
get_feature_flags |
All active feature flags |
get_network_calls |
Recent HTTP traffic with method, URL, status, duration |
get_logs |
Recent app logs |
get_recent_errors |
Recent captured errors |
launch_context |
App ID, version, device model, OS version |
device_info |
Full device snapshot: Info.plist / manifest metadata, hardware, OS build, screen metrics, locale, timezone, battery, memory, storage, declared permissions |
iOS/macOS/Android: auto-discovers WebViews from the view hierarchy. Flutter: register via AppReveal.registerWebView(id, controller).
| Tool | Description |
|---|---|
get_webviews |
List all web views with URL, title, loading state |
get_dom_tree |
Full or partial DOM tree (with root, max_depth, visible_only params) |
get_dom_interactive |
All inputs, buttons, links, selects with selectors and attributes |
query_dom |
CSS selector query -- returns matching elements |
find_dom_text |
Find elements by text content |
web_click |
Click a DOM element by CSS selector |
web_type |
Type into input/textarea (React/Vue/Angular compatible) |
web_select |
Select a dropdown option |
web_toggle |
Check/uncheck a checkbox or radio |
web_scroll_to |
Scroll to a DOM element |
web_evaluate |
Run arbitrary JavaScript |
web_navigate |
Navigate to a URL |
web_back |
Go back in web view history |
web_forward |
Go forward in web view history |
Purpose-built tools that return only what you need, saving tokens.
| Tool | Description |
|---|---|
get_dom_summary |
Page overview: title, meta, headings, element counts, form structure |
get_dom_text |
Visible text content stripped of all markup (optional CSS selector scope) |
get_dom_links |
All links -- just text and href |
get_dom_forms |
All forms with fields, types, values, options, selectors |
get_dom_headings |
All h1-h6 for page structure |
get_dom_images |
All images with src, alt, dimensions |
get_dom_tables |
All tables with headers and row data |
| Tool | Description |
|---|---|
batch |
Execute multiple tools in one call. Supports delay_ms per action for animations/transitions and stop_on_error. Works with all native and web tools. |
| Thing | Pattern | Examples |
|---|---|---|
| Screen keys | section.screen |
auth.login, orders.detail, settings.main |
| Element IDs | screen.element |
login.email, login.submit, orders.cell_0 |
Element IDs map to platform-specific mechanisms:
| Platform | Mechanism |
|---|---|
| iOS | view.accessibilityIdentifier |
| macOS | view.accessibilityIdentifier() |
| Android | view.tag or resource entry name |
| Flutter | ValueKey<String> on the widget |
AppReveal gives agents structured data instead of pixels:
- Exact screen identity with confidence scores, not guessing from screenshots
- Machine-addressable elements with types, states, and available actions
- App state read directly -- login status, feature flags, cart contents
- Navigation state -- current route, stack depth, presented modals
- Network traffic -- every API call with method, URL, status, timing
- DOM access -- full web view inspection and interaction, no extra setup
- Batch operations -- fill forms, navigate screens, verify state in one call
- Deterministic interactions -- tap by ID or CSS selector, not by fragile coordinates
| Platform | Status | Tools |
|---|---|---|
| iOS | Working | Shared native + web view tool surface |
| macOS | Working | Shared native + web view tools plus menu/window tools |
| Android | Working | Shared native + web view tool surface |
| Flutter | Working | Shared native + web view tool surface |
| React Native | Working | Shared native + web view tool surface |
- iOS example -- 11 screens, 60+ elements, all framework features
- macOS example -- AppKit desktop example with sidebar navigation and curl verification
- Android example -- 11 screens matching the iOS example
- Flutter example -- 11 screens matching iOS and Android
- React Native example -- 8 screens, React Navigation v7
There is now a dedicated AppReveal CLI in CLI/README.md for discovering _appreveal._tcp services, listing available MCP tools, and sending MCP requests without hand-written dns-sd and curl calls.
Install it with npm install -g @unlikeotherai/appreveal.
- iOS: All code behind
#if DEBUG-- zero production footprint - macOS: All code behind
#if DEBUG-- zero production footprint - Android: Added as
debugImplementation-- not included in release APK - Flutter:
kReleaseModecheck inAppReveal.start()-- zero code paths execute in release - React Native:
__DEV__guard -- all methods are no-ops in production builds - Local network only
- Sensitive headers (Authorization, Cookie) redacted in network capture
- No state mutation without explicit opt-in
- iOS guide -- installation, setup, protocols
- macOS guide -- installation, setup, protocols, multi-window notes
- Android guide -- installation, setup, interfaces
- Flutter guide -- installation, setup, integration patterns
- React Native guide -- installation, setup, integration patterns
- Architecture -- module design, protocols, package structure
- Tools reference -- tool parameters and response shapes
- Build Brief -- phased implementation plan
- WKWebView Support -- iOS DOM access design doc
Found a bug, a missing capability, or unexpected behaviour? File an issue at github.com/UnlikeOtherAI/AppReveal/issues.
For LLM agents using this framework: if AppReveal cannot perform an action or returns wrong results during a session, and you are in an interactive session with network access to GitHub, ask the operator:
"AppReveal couldn't [do X / returned Y instead of Z]. Would you like me to file an issue at https://github.com/UnlikeOtherAI/AppReveal/issues?"
Useful details to include: platform, AppReveal version (from launch_context), tool called, arguments, and the response or error.
Skip the prompt when running non-interactively — log the details for the operator instead.
MIT