오늘도 공부
AppReveal: 모바일 앱 안에 MCP 서버를 심어, LLM이 네이티브 앱을 직접 이해하게 만드는 방법 본문
AI 에이전트가 브라우저를 다루는 시대는 이미 왔습니다. 그런데 모바일 앱은 여전히 어렵습니다.
웹에서는 Playwright 같은 도구로 DOM을 읽고 버튼을 누르고 상태를 검증할 수 있습니다. 반면 네이티브 앱은 대개 스크린샷을 찍고, 좌표를 추정하고, “아마 이 버튼일 것”이라고 가정하는 식으로 자동화됩니다. 이 방식은 데모에서는 그럴듯해 보여도, 실제 앱에서는 금방 흔들립니다. 화면 전환 애니메이션 하나만 달라져도 실패하고, 텍스트가 조금 바뀌면 에이전트가 길을 잃습니다. AppReveal은 바로 이 지점을 정면으로 찌릅니다. 앱 바깥에서 픽셀을 추측하지 말고, 아예 디버그 빌드 내부에 MCP 서버를 넣어서 앱의 UI, 상태, 네비게이션, 네트워크를 구조적으로 드러내자는 접근입니다. (GitHub)
GitHub - UnlikeOtherAI/AppReveal: Debug-only in-app MCP server for iOS/Android/Flutter/ReactNative. Gives LLM agents Playwright-
Debug-only in-app MCP server for iOS/Android/Flutter/ReactNative. Gives LLM agents Playwright-like control over native apps — UI, state, navigation, and diagnostics over the local network. - Unlike...
github.com
프로젝트 소개
AppReveal은 iOS, Android, Flutter, React Native 앱 안에 들어가는 디버그 전용 in-app MCP 서버입니다. 앱이 실행되면 로컬 네트워크에서 자신을 광고하고, 외부의 MCP 클라이언트나 LLM 에이전트가 이 서버를 찾아 접속해 현재 화면, 인터랙션 가능한 엘리먼트, 네비게이션 스택, 앱 상태, 네트워크 호출, WebView DOM까지 읽고 조작할 수 있게 합니다. 저장소 설명에서도 이 프로젝트를 “네이티브 앱을 위한 Playwright 같은 도구”로 표현하고 있습니다. (GitHub)
기술적으로도 흥미롭습니다. iOS는 NWListener와 Bonjour/mDNS를 사용하고, Android는 NanoHTTPD와 NsdManager를 사용합니다. iOS 쪽은 UIView 트리와 WKWebView, Android 쪽은 ViewGroup 트리와 android.webkit.WebView를 기반으로 화면 구조와 웹 콘텐츠를 노출합니다. 즉, 이 프로젝트는 단순한 테스트 유틸리티가 아니라 플랫폼별 UI 런타임을 MCP 툴셋으로 통일하는 어댑터 레이어에 가깝습니다. (GitHub)
또 하나 중요한 점은 릴리스 빌드에 영향이 거의 없도록 설계되어 있다는 것입니다. iOS는 #if DEBUG, Android는 debugImplementation과 appreveal-noop, Flutter는 release 모드에서 no-op, React Native는 __DEV__ 가드로 보호됩니다. 즉, 운영 배포용 기능이 아니라 개발·디버깅·AI 보조 테스트에 초점을 맞춘 도구입니다. (GitHub)
왜 이 프로젝트가 등장했을까
모바일 앱 자동화가 어려운 이유는 단순합니다. 앱의 의미 있는 상태가 화면 픽셀 뒤에 숨어 있기 때문입니다.
예를 들어 로그인 화면을 자동화한다고 해보겠습니다. 스크린샷 기반 접근은 “이쯤에 이메일 입력창이 있을 것”, “이 버튼이 로그인 버튼일 것”을 추정해야 합니다. 하지만 AppReveal은 다른 길을 택합니다.
- 현재 화면이 auth.login인지 바로 알려준다
- 입력창 ID가 login.email, login.password인지 구조적으로 알려준다
- 로그인 후 네비게이션 스택이 어떻게 바뀌었는지 알려준다
- 실제 네트워크 요청이 성공했는지도 보여준다
- WebView가 섞여 있어도 DOM을 바로 읽는다
이 차이는 큽니다. 픽셀 자동화가 “보이는 것”을 흉내 낸다면, AppReveal은 앱이 내부적으로 알고 있는 것을 에이전트에게 넘깁니다. README도 이 프로젝트의 강점을 “screenshot-only automation”과 대비하면서, 화면 식별, 구조화된 요소, 상태 스냅샷, 네비게이션 상태, 네트워크 트래픽, DOM 접근, 결정적 상호작용을 핵심 차별점으로 설명합니다. (GitHub)
즉, 이 프로젝트가 등장한 배경은 명확합니다. LLM이 앱을 더 잘 조작하게 하려면, 앱이 스스로 자신을 설명할 수 있어야 한다는 것입니다. MCP는 그 설명을 표준화된 툴 인터페이스로 노출하는 수단이고, AppReveal은 그 인터페이스를 모바일 앱 런타임에 박아 넣은 구현체라고 볼 수 있습니다. (GitHub)
핵심 기능
1) 화면과 요소를 “픽셀”이 아니라 “구조”로 노출한다
AppReveal은 get_screen, get_elements, get_view_tree 같은 도구를 통해 현재 화면과 상호작용 가능한 엘리먼트를 구조적으로 제공합니다. 각 엘리먼트는 ID, 타입, 프레임, 액션 가능 여부 같은 정보를 가질 수 있습니다. iOS는 accessibilityIdentifier, Android는 view.tag나 리소스 이름, Flutter는 ValueKey<String>, React Native는 testID를 활용합니다. (GitHub)
개발자 관점에서는 이 부분이 가장 실용적입니다. UI 테스트나 AI 에이전트 제어에서 가장 취약한 고리가 “대상이 무엇인지 정확히 식별하는 문제”인데, AppReveal은 여기에 명시적 식별자 규약을 넣습니다.
#if DEBUG
extension LoginViewController: ScreenIdentifiable {
var screenKey: String { "auth.login" }
var screenTitle: String { "Login" }
}
#endif
emailField.accessibilityIdentifier = "login.email"
passwordField.accessibilityIdentifier = "login.password"
이런 식으로 화면과 요소에 안정적인 이름을 부여하면, 에이전트는 좌표가 아니라 의미 기반으로 행동할 수 있습니다. iOS 가이드도 화면 키와 접근성 식별자 부여를 핵심 통합 포인트로 제시합니다. (GitHub)
2) 앱 상태와 네비게이션을 직접 읽는다
AppReveal의 진짜 힘은 get_state, get_navigation_stack, get_feature_flags 같은 도구에 있습니다. 에이전트는 “지금 주문 상세 화면인가?”를 추론할 필요가 없습니다. 상태와 라우팅 정보를 직접 받으면 됩니다. 툴 문서의 예시에서도 get_state는 로그인 상태나 장바구니 개수 같은 앱 스냅샷을 반환하고, get_navigation_stack은 현재 라우트와 전체 스택, 모달 상태를 보여줍니다. (GitHub)
이건 단순 편의 기능이 아닙니다. 모바일 자동화에서 실패 원인의 상당수는 “현재 어디에 있는지”를 잘못 아는 데서 시작합니다. AppReveal은 이걸 화면 OCR이나 이미지 분석이 아니라 앱 소유의 라우터와 상태 컨테이너를 연결하는 브리지로 해결합니다. 아키텍처 문서에서도 StateBridge가 상태, 네비게이션, 피처 플래그를 프로토콜로 읽어들이는 구조라고 설명합니다. (GitHub)
Android 예시를 보면 이 철학이 더 잘 드러납니다.
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
AppReveal.start(this)
AppReveal.registerStateProvider(myStateContainer)
AppReveal.registerNavigationProvider(myRouter)
AppReveal.registerFeatureFlagProvider(myFeatureFlags)
AppReveal.registerNetworkObservable(myNetworkClient)
}
}
}
여기서 AppReveal은 앱 바깥의 테스트 러너가 아니라, 앱 내부의 실제 상태 시스템과 연결된 관찰자 역할을 합니다. (GitHub)
3) 네트워크와 진단 정보를 함께 본다
모바일 UI 자동화가 답답한 이유 중 하나는 “왜 실패했는지”를 알기 어렵다는 점입니다. 버튼을 눌렀는데 화면이 안 바뀌면, 원인이 네트워크 지연인지, API 오류인지, 클라이언트 예외인지, 상태 갱신 누락인지 알기 어렵습니다.
AppReveal은 get_network_calls, get_logs, get_recent_errors, device_info를 통해 이 영역까지 함께 노출합니다. 특히 device_info는 앱/OS/디바이스/로케일/배터리/메모리/스토리지/권한 등 런타임 환경을 한 번에 반환하도록 설계돼 있습니다. (GitHub)
아키텍처 문서상으로도 NetworkObserver, DiagnosticsBridge, DebugOverlay가 별도 모듈로 분리되어 있습니다. 네트워크 요청은 링 버퍼에 저장하고, 오류와 메트릭을 묶어서 볼 수 있게 합니다. 이 구조는 단순 테스트 자동화보다, AI 기반 QA/디버깅 어시스턴트에 더 가깝습니다. 에이전트가 “로그인 버튼을 눌렀지만 401이 났기 때문에 화면이 유지됐다”는 식의 설명을 할 수 있게 되기 때문입니다. (GitHub)
4) WebView를 별도 세계로 취급하지 않는다
실무 앱은 순수 네이티브만으로 이루어지지 않습니다. 결제, 인증, 고객센터, 하이브리드 콘텐츠처럼 WebView가 자주 섞입니다. 이런 앱에서 자동화가 특히 복잡해지죠.
AppReveal은 이 부분도 잘 파고듭니다. get_webviews, get_dom_tree, query_dom, web_click, web_type 같은 도구뿐 아니라, get_dom_summary, get_dom_text, get_dom_links, get_dom_forms, get_dom_tables처럼 토큰 효율적인 쿼리 도구도 제공합니다. 즉, 에이전트가 전체 DOM을 매번 다 읽지 않아도, 필요한 정보만 저비용으로 가져올 수 있습니다. (GitHub)
이 설계는 LLM 친화적입니다. DOM 전체를 덤프하면 컨텍스트를 금방 소모하지만, “링크만”, “폼 구조만”, “헤딩만” 같은 질의는 훨씬 효율적입니다. 이 프로젝트가 단순한 앱 디버거가 아니라 에이전트 실행 비용까지 고려한 MCP 도구 설계를 하고 있다는 뜻입니다. (GitHub)
5) 여러 동작을 한 번에 묶는 배치 실행
LLM 에이전트가 매 동작마다 네트워크 왕복을 하다 보면 느리고 불안정해집니다. AppReveal은 batch 도구를 통해 여러 툴 호출을 순차 실행할 수 있고, 각 액션에 delay_ms를 둘 수 있으며 stop_on_error도 지원합니다. 툴 문서에는 로그인 폼 입력 → 제출 → 화면 검증까지 한 번에 묶는 예시가 실려 있습니다. (GitHub)
이 기능은 실무적으로 꽤 중요합니다. 특히 모바일 앱은 애니메이션, 로딩, 비동기 상태 반영 때문에 액션 간 간격 제어가 필요합니다. batch는 그런 현실을 반영한 기능입니다.
{
"tool": "batch",
"arguments": {
"actions": [
{ "tool": "type_text", "arguments": { "element_id": "login.email", "text": "user@example.com" } },
{ "tool": "type_text", "arguments": { "element_id": "login.password", "text": "password" } },
{ "tool": "tap_element", "arguments": { "element_id": "login.submit" } },
{ "tool": "get_screen", "arguments": {}, "delay_ms": 800 }
],
"stop_on_error": true
}
}
이런 형태면 에이전트는 “행동 시퀀스”를 보다 원자적으로 실행할 수 있습니다. (GitHub)
프로젝트 아키텍처 분석
AppReveal의 구조는 크게 보면 세 층으로 나뉩니다.
- 앱 내부 수집/제어 계층
화면 식별, 엘리먼트 수집, 상호작용, 상태 조회, 네트워크 관찰, 진단 수집을 담당합니다. - 임베디드 MCP 서버 계층
디버그 빌드 내부에서 HTTP 기반 MCP 엔드포인트를 열고, 툴 메타데이터와 호출을 처리합니다. - 외부 에이전트 계층
Bonjour/mDNS로 서버를 찾고, MCP 클라이언트로 툴을 호출하며, LLM이 추론을 담당합니다. (GitHub)
이를 Mermaid로 그리면 다음과 같습니다.

이 구조의 핵심은 앱 내부 정보를 플랫폼 공통 MCP 도구로 추상화한다는 점입니다. README는 네 플랫폼이 동일한 이름과 파라미터, 응답 형태의 도구를 노출한다고 설명하고, 아키텍처 문서는 iOS 쪽 모듈을 MCPServer, BonjourDiscovery, ScreenResolver, ElementInventory, InteractionEngine, NetworkObserver, StateBridge, DiagnosticsBridge 등으로 분리해 설명합니다. (GitHub)
개발자 입장에서 이건 상당히 좋은 설계입니다. 이유는 두 가지입니다.
첫째, 도구 인터페이스가 플랫폼보다 위에 있다는 점입니다. 앱 구현은 Swift, Kotlin, Dart, TypeScript로 흩어져 있어도, 에이전트가 보는 인터페이스는 거의 동일합니다. 즉, 멀티플랫폼 팀이 하나의 AI 테스트/디버깅 플로우를 공유할 수 있습니다. (GitHub)
둘째, 플랫폼 특수성을 완전히 숨기지는 않는다는 점입니다. 예를 들어 iOS는 NWListener, Android는 NanoHTTPD, 뷰 탐색 방식도 다릅니다. 하지만 이 차이를 내부 구현으로 감추고, 바깥에는 MCP 도구라는 공통 계약만 노출합니다. 이것이 AppReveal을 단순 SDK가 아니라 플랫폼별 런타임 어댑터 묶음으로 보게 만드는 이유입니다. (GitHub)
실제로 어떻게 붙이는가
iOS
iOS에서는 Swift Package Manager로 추가하고, 디버그 빌드에서 AppReveal.start()를 호출합니다. 필요하면 ScreenIdentifiable을 구현하고 accessibilityIdentifier를 부여합니다. 플랫폼 세부 구현은 NWListener, Bonjour, UIView 순회, WKWebView 기반입니다. (GitHub)
#if DEBUG
import AppReveal
#endif
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ...) -> Bool {
#if DEBUG
AppReveal.start()
#endif
return true
}
Android
Android는 debugImplementation("com.appreveal:appreveal")와 releaseImplementation("com.appreveal:appreveal-noop") 구성을 사용합니다. 이 설계 덕분에 릴리스에는 no-op 스텁만 남고, 실제 구현은 빠집니다. 상태·네비게이션·피처 플래그·네트워크 옵저버를 선택적으로 등록할 수 있습니다. (GitHub)
dependencies {
debugImplementation("com.appreveal:appreveal")
releaseImplementation("com.appreveal:appreveal-noop")
}
Flutter
Flutter는 AppReveal.start()로 시작하고, 앱을 AppReveal.wrap()으로 감싸며, navigatorObservers에 AppReveal.navigatorObserver를 붙입니다. README 기준으로 iOS/Android/Flutter는 44개 도구를 제공한다고 안내합니다. (GitHub)
void main() {
WidgetsFlutterBinding.ensureInitialized();
AppReveal.start();
runApp(AppReveal.wrap(const MyApp()));
}
MaterialApp(
navigatorObservers: [AppReveal.navigatorObserver],
)
React Native
React Native는 AppReveal.start(), AppRevealFetchInterceptor.install(), useAppRevealScreen, createNavigationListener 같은 API를 제공합니다. 특히 React Navigation과 fetch 가로채기를 공식 통합 포인트로 제시하는 점이 실용적입니다. 다만 저장소 메인 README에서는 React Native가 “in development”로 표시되어 있고, 메인 설명은 44개 도구를 말하는 반면 React Native README는 43개 도구라고 적고 있어, 이 부분은 아직 빠르게 진화 중인 영역으로 보는 게 맞습니다. (GitHub)
import { AppReveal, AppRevealFetchInterceptor } from 'react-native-appreveal';
useEffect(() => {
if (__DEV__) {
AppReveal.start();
AppRevealFetchInterceptor.install();
}
}, []);
이 프로젝트가 특히 잘 맞는 사용처
1) AI 에이전트 기반 모바일 QA
가장 직접적인 사용처입니다. 테스트 시나리오를 자연어로 주고, 에이전트가 화면 상태를 읽고 조작하게 할 수 있습니다. 이때 중요한 건 성공/실패 판정이 픽셀이 아니라 상태·네비게이션·네트워크 기준으로 가능하다는 점입니다. (GitHub)
예를 들면 이런 흐름입니다.
1. get_screen
2. get_elements
3. type_text(login.email)
4. type_text(login.password)
5. tap_element(login.submit)
6. get_network_calls
7. get_navigation_stack
8. get_state
이렇게 하면 “로그인 버튼 눌렀다”에서 끝나는 게 아니라, “로그인 API가 200이었고, 현재 라우트가 orders.list이며, state.isLoggedIn=true다”까지 확인할 수 있습니다. (GitHub)
2) 디버깅 보조 에이전트
개발자 입장에서 더 재미있는 사용처는 이쪽입니다. 버그 리포트가 들어왔을 때, 에이전트가 앱에 붙어서 현재 화면 구조, 최근 네트워크 호출, 최근 오류, 디바이스 정보를 함께 읽고 원인을 좁혀갈 수 있습니다. device_info와 get_recent_errors 조합은 특히 재현이 까다로운 환경 의존 버그에 유용합니다. (GitHub)
3) 하이브리드 앱 검사
네이티브 화면과 WebView가 섞인 앱에서 자동화 도구를 두 벌 운영하는 경우가 많습니다. AppReveal은 이 둘을 같은 MCP 표면 아래에 묶으려 합니다. 네이티브 화면에서는 tap_element, WebView에서는 web_click을 쓰면 됩니다. 에이전트 입장에서는 같은 세션 안에서 둘을 오갈 수 있습니다. (GitHub)
개발자 관점에서 느껴지는 장점
첫째, 기존 앱 구조를 존중한다는 점입니다. 새로운 테스트 DSL을 강요하기보다, 이미 존재하는 화면 키, 접근성 식별자, 라우터, 상태 컨테이너, 네트워크 클라이언트를 연결점으로 활용합니다. (GitHub)
둘째, 멀티플랫폼 통일성이 좋다는 점입니다. Swift/Kotlin/Dart/TypeScript 구현이 달라도 에이전트가 호출하는 도구는 비슷합니다. 팀 차원에서 모바일 AI 자동화 인터페이스를 표준화하기 좋습니다. (GitHub)
셋째, LLM 친화적이다는 점입니다. 구조화된 응답, 토큰 절약형 DOM 쿼리, 배치 실행 같은 선택은 “사람이 보는 디버거”보다 “에이전트가 쓰는 디버거”에 가깝습니다. (GitHub)
아쉬운 점도 있다
이 프로젝트는 매우 흥미롭지만, 아직 초기 단계의 느낌도 분명히 있습니다. 저장소의 커밋 수와 스타 수가 많지 않고, React Native는 아직 개발 중으로 표기되어 있으며, 문서 일부에서는 도구 수가 44와 43으로 엇갈립니다. 즉, 지금 당장 대규모 프로덕션 표준으로 보기보다는, 빠르게 실험하고 팀의 개발 경험을 앞당기기 좋은 오픈소스에 가깝습니다. (GitHub)
또한 이런 방식은 앱이 어느 정도 의미 있는 식별자와 상태 브리지를 제공해야 진가가 납니다. 아무런 screenKey, testID, accessibilityIdentifier 없이 “그냥 붙이면 다 된다”는 식은 아닙니다. 하지만 반대로 말하면, 이미 테스트 가능성을 고민하던 팀이라면 이 프로젝트는 그 노력에 AI 인터페이스를 얹어주는 도구가 될 수 있습니다. (GitHub)
언제 쓰면 좋은가
AppReveal은 이런 팀에 특히 잘 맞습니다.
- 모바일 앱을 AI 에이전트로 테스트하거나 디버깅하고 싶은 팀
- UI 자동화를 스크린샷 추론이 아니라 구조화된 상태 기반으로 바꾸고 싶은 팀
- WebView가 섞인 하이브리드 앱을 하나의 인터페이스로 다루고 싶은 팀
- iOS/Android/Flutter/React Native를 함께 운영하면서 공통 자동화 표면이 필요한 팀 (GitHub)
반대로, 식별자 규약도 없고 상태 제공자도 붙일 수 없으며 로컬 네트워크 기반 디버그 노출 자체가 부담스러운 조직이라면 도입 장벽이 있을 수 있습니다. 이 도구는 “완전 무설정”보다는 잘 설계된 앱에 더 많은 관측성과 제어권을 주는 SDK에 가깝기 때문입니다. (GitHub)
마무리
AppReveal의 핵심 아이디어는 단순하지만 강력합니다.
앱을 자동화 가능한 대상으로 보지 말고, 스스로를 설명할 수 있는 시스템으로 만들자.
이 한 문장으로 이 프로젝트를 설명할 수 있습니다. LLM이 모바일 앱을 잘 다루지 못하는 이유는 모델이 멍청해서가 아니라, 앱이 구조화된 인터페이스를 주지 않았기 때문일 수 있습니다. AppReveal은 그 인터페이스를 MCP라는 표준 위에 올립니다. 그래서 이 프로젝트는 “모바일 테스트 도구”라기보다, 모바일 앱을 에이전트 친화적으로 바꾸는 런타임 레이어로 보는 편이 더 정확합니다. (GitHub)
'AI' 카테고리의 다른 글
| OpenGranola: 회의 중 실시간으로 “할 말을 찾아주는” 로컬 AI 미팅 코파일럿 (1) | 2026.03.18 |
|---|---|
| workerd: Cloudflare Workers를 로컬과 프로덕션으로 확장하는 JavaScript/Wasm 런타임 (0) | 2026.03.18 |
| 🚀 AI 파인튜닝, 딸깍으로 가능한 Unsloth Studio 완전 분석 (0) | 2026.03.18 |
| GitNexus: 코드베이스를 지식 그래프 형태로 변환 (0) | 2026.03.17 |
| AimangaStudio (만화도 AI로 딸깍?) (0) | 2026.03.17 |
