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()
#endiftap_point and tap_element on SwiftUI views require private UIKit APIs on iOS 26+. AppReveal compiles this code in automatically for debug builds and compiles it out for release builds — no private symbols reach your App Store binary. No setup needed.
To opt out of private APIs entirely (even in debug builds), remove the swiftSettings line from AppReveal's Package.swift. See iOS guide for details.
// 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 -- 12 screens, 60+ elements, all framework features
- macOS example -- AppKit desktop example with sidebar navigation and curl verification
- Android example -- 12 screens matching the iOS example
- Flutter example -- 11 screens matching iOS and Android
- React Native example -- 9 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