내 안드로이드 앱을 로컬 MCP 서버로 만드는 법: Android MCP와 Jetpack AppFunctions(Beta) 해부
Android Dev Summit 2026에서 발표된 온디바이스 함수 호출 프레임워크 Android MCP(Jetpack AppFunctions 1.0.0-beta01)의 아키텍처와 구현 원칙을 정리한다. 서버 사이드 MCP와의 역할 경계, Binder IPC 및 KDoc 스키마 컴파일 구조, Kotlin 기본값 무시와 UI 스레드 실행 등 실무 주의점을 살펴본다.
시스템·인앱 에이전트 클라이언트(좌측), Android OS 도구 레지스트리 및 Binder IPC 버스(중앙), 그리고 로컬 MCP 서버로 동작하는 Android 앱의 Jetpack AppFunctions 모듈(우측)을 형상화한 3D 아이소메트릭 개념 일러스트다. 실제 아키텍처 콘솔 화면이 아니다.
Google이 공개한 Android Dev Summit 2026 인텔리전스 시스템 통합 세션 영상(Integrate your app with the Android intelligence system)과 Jetpack AppFunctions 공식 문서는 사용자가 여러 앱을 일일이 오가며 작업하던 방식(App hopping)에서 벗어나, 시스템 에이전트(Gemini 등)나 인앱 에이전트가 단말기 내부의 앱 기능을 직접 호출하는 Android MCP 아키텍처를 다룬다. Android 개발자 관계 엔지니어 Dan Galpin과 프로덕트 매니저 Sarah Shamsi는 베타(1.0.0-beta01)로 진입한 androidx.appfunctions의 구조와 Binder IPC 기반 전송 계층, 그리고 코틀린 기본값 무시·UI 스레드 실행 등 실무 구현 시 반드시 피해야 할 함정을 상세히 공개했다.
결론부터 말하면, 클라우드 백엔드 API는 서버 사이드 MCP로 노출하더라도 종단간 암호화(E2EE)된 로컬 데이터·오프라인 동작·단말기 센서(UWB 등)·클라이언트 전용 앱 기능은 Android OS가 레지스트리 역할을 맡고 Binder IPC로 통신하는 온디바이스 Android MCP(Jetpack AppFunctions)로 구현해야 하며, 이때 생략된 인자가 null·false·0으로 들어오는 특성과 메인 스레드 기본 실행 규칙을 반드시 코드에서 직접 처리해야 한다. 다만 이 글은 Google의 공식 발표 영상과 개발자 문서를 교차 검증해 정리한 것이며, 실제 상용 앱에서 Connected Apps Platform 프리뷰 승인 절차를 거쳐 트래픽을 실측한 결과는 아니다.
서버 사이드 MCP와 온디바이스 Android MCP(AppFunctions)의 구조 및 역할 경계
Android Dev Summit 2026 — Integrate your app with the Android intelligence system 공식 세션 썸네일 (원본 JPEG)
표준 MCP(Model Context Protocol)가 서버와 클라이언트 사이의 도구 호출 규약이라면, Android MCP는 이 구조를 단말기 내부로 그대로 가져온 프레임워크다(AppFunctions 인텔리전스 시스템 통합 블로그).
- 역할의 1:1 매핑: 개발자의 **Android 앱이 로컬 MCP 서버(Server)**가 되고, **시스템 에이전트(Gemini)나 인앱 에이전트(
ML Kit GenAI)가 MCP 클라이언트(Client)**가 되며, Android OS 플랫폼이 중앙 도구 레지스트리(Tool Registry) 역할을 수행한다.
- 전송 계층과 스키마 컴파일:
Streamable HTTP나stdio대신 Android 고유의 최적화된 Binder IPC 위에서 동작한다. KSP(Kotlin Symbol Processing) 컴파일러가@AppFunctionDeclaration및@AppFunctionSerializable어노테이션과 KDoc 주석을 읽어 XML 스키마 파일을 생성하면, Android OS가 이를 색인(index)해 두었다가 권한(EXECUTE_APP_FUNCTIONS)을 가진 에이전트의 요청에 맞춰 실행한다.
발표(04:35~11:15)에서는 시스템 에이전트와 앱을 연동하는 세 가지 경로의 선택 기준을 다음과 같이 비교한다.
| 통합 경로 | 핵심 동작 방식 | 가장 적합한 실무 시나리오 및 대표 사례 |
| 앱 내부의 Kotlin 함수를 Binder IPC를 통해 로컬 에이전트에 직접 노출 (1.0.0-beta01) | ① 종단간 암호화(E2EE) 로컬 데이터 (서버에 복호화 키가 없는 메신저 대화 검색) ② 오프라인·초저지연 실행 ( Samsung Health Wear OS 앱의 로컬 달리기 측정 시작)③ 단말기 하드웨어·센서 직결 ( UWB 초광대역 통신을 이용한 차량 도어 핸즈프리 잠금 해제)④ 클라우드 서버가 없는 로컬 앱 ( Xiaomi Gallery 로컬 사진 검색, Pixel·Lenovo 단말의 OS 시스템 설정 변경 기능인 Device Help) | |
| 서버 사이드 MCP (Server-side MCP) | 클라우드 백엔드 엔드포인트를 원격 MCP 서버로 구축하여 Google Connected Apps 플랫폼에 등록 | 서버 DB와 비즈니스 로직이 중심이 되는 예약·결제·계정 동기화 작업 |
| 플랫폼 주도 UI 자동화 (Computer Control APIs) | 개발자 코드 수정 없이 OEM 탑재 에이전트가 화면을 분석하고 터치 이벤트를 시뮬레이션 | 전용 함수가 아직 노출되지 않은 롱테일 작업 (예: Expedia 앱 화면을 에이전트가 백그라운드에서 직접 탐색해 6인 투어 옵션을 찾은 뒤, 최종 결제 직전에 멈춰 사용자에게 화면 제어권 인계) |
Kotlin 코드로 보는 AppFunctions 구현과 KDoc 스키마 설계
샘플 여행 앱 JetPacker의 구현 예제를 보면, 먼저 AppFunctionService를 상속한 추상 클래스에 @AppFunctionServiceEntryPoint와 @AndroidEntryPoint(Hilt 의존성 주입)를 붙여 로컬 MCP 서버 진입점을 만든다(AppFunctions 추가 가이드).
이때 가장 중요한 점은 **KDoc 주석이 단순한 개발자용 메모가 아니라 에이전트 LLM이 읽는 컴파일된 API 스키마 자산(Compiled API Asset)**이라는 사실이다. isDescribedByKDoc = true를 설정하면 함수와 데이터 클래스의 KDoc이 그대로 에이전트의 도구 선택 기준으로 들어간다(appfunctions 1.0.0-beta01 릴리스 노트).
또한 개별 함수의 KDoc만으로는 에이전트가 ‘어떤 함수를 먼저 호출해야 하는지’ 알기 어렵기 때문에, 매니페스트에 등록하는 app_metadata.xml 파일에 ① 앱의 핵심 도메인 한 줄 요약, ② 선행 조건 등 작업 순서(Operational patterns), ③ 입력 데이터의 논리적 제약(Constraints)을 함께 정의해 두어야 에이전트가 여러 도구를 올바른 순서로 조합한다.
아울러 AccessLevel 설정(SELF, SYSTEM, TRUSTED)을 통해 한 번 정의한 AppFunction을 **자체 인앱 에이전트(ML Kit GenAI) 전용(SELF)**으로만 쓸지, **OS 시스템 전용(SYSTEM)**으로 열지, **Gemini 같은 신뢰된 외부 시스템 에이전트(TRUSTED)**까지 허용할지 세밀하게 통제할 수 있으며, 로그인 여부에 따라 런타임에 함수를 동적으로 활성화·비활성화하거나 특정 화면(Activity/UI 코루틴 스코프)에만 바인딩되는 스코프 함수를 만들 수도 있다.
현업 앱에 AppFunctions를 구현·테스트할 때 반드시 피해야 할 4가지 함정
발표 후반부(12:54~23:45)에서는 일반 유닛 테스트에서는 잘 드러나지 않지만 실제 에이전트가 호출할 때 장애를 일으키는 네 가지 핵심 주의사항과 테스트 절차를 강조했다.
- Kotlin 파라미터 기본값(Default Value) 무시 현상 대비: 에이전트가 선택적 파라미터를 생략하면 Kotlin 코드에 적어둔 기본값(예:
val limit: Int = 10)이 작동하지 않고null·false·0이 그대로 전달된다. 따라서 선택적 인자는 반드시 Nullable 타입(Int?)으로 선언하고 함수 본문 안에서limit ?: 10형태로 기본값을 직접 할당해야 한다.
- 메인(UI) 스레드 실행으로 인한 ANR 방지 및
AppFunctionException반환: 모든AppFunction호출은 기본적으로 메인(UI) 스레드에서 시작된다. DB 조회나 파일·네트워크 I/O를 그대로 실행하면 ANR이 발생하므로 반드시withContext(Dispatchers.IO)등 백그라운드 디스패처로 전환해야 한다. 또한 일반 예외(IllegalArgumentException등)를 던지면 에이전트에게는 불투명한UnknownError로만 전달되므로, 에이전트가 스스로 파라미터를 고쳐 재시도할 수 있도록 구체적인 원인을 담은AppFunctionException을 던져야 한다.
- 데이터 유출(Exfiltration)을 부르는 포괄적(Catch-all) 파라미터 금지: 함수 인자에
fullContext나conversationHistory처럼 모호하고 넓은 문자열 파라미터를 두면 LLM이 사용자의 민감한 이전 대화 전체를 외부 함수로 넘겨버릴 위험이 있으므로, 꼭 필요한 최소한의 명시적 필드만 선언해야 한다.
- ADB 명령어(
cmd app_function) 및 Google Connected Apps/Play 콘솔 연동 검증: Android 17 이상 단말·에뮬레이터에서는adb shell cmd app_function list-app-functions로 함수 등록 여부를 확인하고execute-app-function으로 JSON 인자를 직접 넣어 실행해 볼 수 있다. 이후 실제 Gemini 앱과 연동할 때는 Google Connected Apps 콘솔에서Android MCP앱을 생성하고 Play 콘솔 내부 테스트 트랙에.aab번들을 업로드해 함수들을 자동 감지·연결한 뒤, 두 콘솔 모두에 동일한 테스터 이메일 목록을 등록해야 정상 동작한다.
결국 인텔리전스 시대의 앱 경쟁력은 사용자가 앱 아이콘을 직접 누르기를 기다리는 데서 끝나지 않고, 사용자가 잠금 화면·키보드·워치·차량 어디서든 의도(Intent)만 말하면 에이전트가 즉시 호출할 수 있도록 잘 문서화된 로컬 도구(AppFunctions)를 노출하는가에서 갈린다.
참고 자료
발표 내용과 라이브러리 사양은 November 1, 2026 공개 자료 확인 기준이다.