AppFunctions Is Android's On-Device MCP, Letting Gemini Call Your App's Code Directly
Foreword
Today, Google officially released AppFunctions on Android Developers.
That is, the Android version of MCP. Today Carson will thoroughly explain this API!
1. What is AppFunctions?
AppFunctions = An official protocol for Android Apps to 'standardize the exposure' of their functions to AI Agents.
It consists of three parts:
- 1: A set of Jetpack annotations (
@AppFunction,@AppFunctionSerializable); - 2: A KSP compile-time processor (converts annotations into XML schema);
- 3: An OS-level registry (maintained by the Android platform, through which Agents discover your App).
Comparison with previous solutions
Over the past few years, people on Android have actually been trying to solve the problem of 'how AI calls Apps':
- Intent / Deep Link: Coarse-grained, few parameters, requires App foreground or complex configuration;
- App Actions: A product of the Assistant era, bound to predefined intents, poor scalability;
- Slice / Widgets: UI fragments for users to see, not invocation interfaces for AI;
- Accessibility simulated clicks: Dirty, slow, fragile, not discussed.
And the universal solution developed by the entire LLM ecosystem in 2024–2025 is called MCP (Model Context Protocol)—an open protocol that allows LLMs to discover and call tools. The problem is: MCP is designed for the cloud, running on servers, exposing tools to cloud LLMs over the network.
What Google did this time is bring the MCP concept to mobile phones.
This is the historical position of AppFunctions—the first invocation protocol on Android specifically designed for AI Agents.
2. Why is it called a 'Local MCP'?
Official documentation:
"AppFunctions is an Android platform API with an accompanying Jetpack library to simplify Android MCP integration."
Translated, that means—AppFunctions ≈ an MCP server local to the Android device.
Cloud MCP vs Local AppFunctions
| Dimension | Standard MCP (Cloud) | AppFunctions (Local) |
|---|---|---|
| Execution Location | Server | Device Local |
| Network Dependency | Must be online | Not needed |
| Latency | Network round-trip | No round-trip |
| State Access | Must be maintained outside App | Directly reuses App's existing state |
| Privacy | Data leaves device | Data stays on device |
| Integration Method | External service | OS-level hook |
The most easily overlooked point: Directly reusing App's existing state
This point is severely underestimated.
Imagine you are writing a bookkeeping App.
For Cloud MCP to let an LLM help users record expenses, you have to rebuild an entire set of business logic on the server first—database, account system, authentication, synchronization... the workload is comparable to building another backend.
But with AppFunctions, Gemini directly calls the addExpense() that already exists in your App. The Room database, DI container, Repository layer—everything you've already written, the Agent can use.
Cloud MCP is 'building a new backend from scratch', AppFunctions is 'adding a door to your existing App'.
3. Architecture Breakdown: Three Roles, One Chain
- MCP Server = Your App: Declares exposable functions;
- Registry = Android Platform: Maintains a device-wide AppFunctions registry;
- MCP Client = System Agent (e.g., Gemini): After gaining system-level privileges, accesses the registry to discover and invoke tools.
The complete chain of a typical invocation
A user says "Add a $5 coffee to the Paris trip", what happens behind the scenes?
User voice: "Add a $5 coffee to the Paris trip"
│
▼
Gemini (Agent) parses intent, determines available AppFunction
│
▼
Queries Android platform's AppFunction metadata (including KDoc descriptions)
│
▼
Gemini selects `addExpense`, parses out parameters
│ {tripName: "Paris", amount: 5, currency: "USD", category: "Food"}
▼
System calls your App's `addExpense()` in the background
│
▼
Function returns result → Agent summarizes → User sees confirmation
Two counter-intuitive technical details
Seeing this, you might think "That's it? No different from writing a regular function." But there are two pitfalls that the official blog specifically called out.
① KDoc is the 'AI Prompt'
The KDoc comments you write on your functions will be compiled into XML schema by KSP, ultimately becoming the tool description seen by the LLM.
/**
* Adds a travel expense record.
*
* @param tripId The unique ID of the trip, required.
* @param amount The amount, unit determined by currency.
* @param currency ISO 4217 currency code, e.g., "USD", "CNY".
* @param category Expense category, e.g., "Food", "Transport", "Accommodation", optional.
*/
@AppFunction(isDescribedByKDoc = true)
suspend fun addExpense(
tripId: String,
amount: Double,
currency: String,
category: String? = null,
): Expense = TODO()
The better the KDoc is written, the more accurately Gemini calls it.
This is completely different from the traditional Android development comment culture—in the past, we wrote KDoc for colleagues to read, now it's for AI to read.
If your comments are perfunctory, the Agent might fill currency with "Dollars" instead of "USD", and your addExpense will directly report a parameter error.
② AppFunction runs on the UI thread by default
This is a pitfall explicitly pointed out in the official blog. All AppFunctions execute on the UI thread by default; operations involving IO or databases must explicitly switch threads:
@AppFunction(isDescribedByKDoc = true)
suspend fun searchTrip(...): List<TripSerializable> {
return withContext(Dispatchers.IO) {
// Database queries, network requests, etc.
}
}
If you don't switch threads, you'll get StrictMode warnings at best, ANR at worst.
Remember these two points, and you'll avoid 80% of integration pitfalls.
4. Practical Integration: Turning Your App into a Gemini Tool
Taking the official Jetpacker (Travel App) 'Search Trips' function as an example, actual integration involves just three steps.
Step 1: Add Dependencies
// build.gradle.kts
implementation("androidx.appfunctions:appfunctions:1.0.0-alpha10")
ksp("androidx.appfunctions:appfunctions-compiler:1.0.0-alpha10")
Note: Currently it is alpha10, the API surface may still change, don't go all-in in production environments yet.
Step 2: Define Serializable Data Models
For Gemini to call your functions and receive return values, the data must be serializable. Use @AppFunctionSerializable to annotate data classes:
@AppFunctionSerializable(isDescribedByKDoc = true)
data class TripSerializable(
/** The trip's unique identifier. */
val id: String,
/** The trip's title. */
val title: String,
/** The trip's destination location. */
val location: String,
/** The trip's start date in milliseconds. */
val startDate: Long,
/** The trip's end date in milliseconds. */
val endDate: Long,
/** A list of participants. */
val participants: List<String>,
)
Every field must have KDoc, because these comments will also be written into the schema for Gemini to see.
Step 3: Expose Functions + Register Service
First, use @AppFunction to mark the functions to expose:
/**
* Looks for trips based on optional filters like id, title, location, and dates.
*
* @param id The unique identifier of the trip.
* @param title The title or name of the trip.
* @param location The destination location.
* @param startDate The minimum start date in milliseconds.
* @param endDate The maximum end date in milliseconds.
* @return A list of trips matching the filters.
*/
@AppFunction(isDescribedByKDoc = true)
suspend fun searchTrip(
id: String? = null,
title: String? = null,
location: String? = null,
startDate: Long? = null,
endDate: Long? = null,
): List<TripSerializable> = withContext(Dispatchers.IO) {
tripDao.search(id, title, location, startDate, endDate)
.map { it.toSerializable() }
}
Then write a Service entry point to mount these AppFunctions:
@RequiresApi(36)
@AndroidEntryPoint
@AppFunctionServiceEntryPoint(
serviceName = "JetPackerAppFunctionService",
appFunctionXmlFileName = "jetpacker_app_function_service"
)
abstract class BaseJetPackerAppFunctionService : AppFunctionService() {
@Inject internal lateinit var tripDao: TripDao
@Inject internal lateinit var expenseDao: ExpenseDao
// Other dependencies...
}
KSP will automatically generate a subclass at compile time; you just need to register the metadata file in AndroidManifest.xml.
Hilt works natively, because AppFunctionService is essentially still an Android Service.
Verification: ADB in one go
At this point, your App is already in the system's AppFunctions registry. On Android 17+, use ADB commands directly to view:
# List all registered AppFunctions on the device
adb shell cmd app_function list-app-functions
# Execute a specific AppFunction (JSON parameters)
adb shell cmd app_function execute-app-function
Google also provides an AppFunctions Testing Agent graphical tool that can directly simulate real conversation flows—much faster than writing your own tests.
In three steps, your App can already be 'seen' by Gemini.
5. The Official Jetpacker Template: What Did They Do?
In the official Google blog, Ben Weiss selected three types of functions where "voice is faster than tapping" for demonstration:
| Function | AppFunction | How users can use it |
|---|---|---|
| Expense Recording | addExpense / getExpenses |
"Add a $5 coffee to the Paris trip" |
| Trip Management | getItinerary / addItineraryEvent |
"What am I doing next in Paris?" |
| Hands-free Notes | addVoiceNote |
Speak a thought while walking, App auto-transcribes and saves |
Selection Logic: Is Voice Faster Than Tapping?
The selection logic here is very important—not all functions are worth making into AppFunctions. The judgment criterion is one sentence: Is voice faster than tapping?
- ✅ Suitable: Clear parameters, simple state, frequently used (bookkeeping, quick search, adding schedule);
- ❌ Not suitable: Requires complex UI decisions (choosing products, editing images, multi-step forms).
Google left multi-step complex processes for the fifth article in the series, which covers A2UI + ADK Agentic Workflow (in-app multi-agent orchestration), a different track that Carson will discuss next time.
First, take a look at a screenshot of the Booking Assistant in Jetpacker—this is a typical scenario for Agentic Workflow:
Remember this iron rule of selection: Only make something an AppFunction if it can be done in one sentence; leave tasks requiring choices to Agentic Workflow.
6. Should Your App Integrate? Three Tiers of Selection
Developers integrating AppFunctions fall into three tiers, find your own level:
L1 · Wait-and-See: Integrate the 3 most frequent functions first
Pick the 3 most frequently used functions in your App (usually search, create, quick switch).
Wrap them as AppFunctions, write good KDoc, let Gemini discover and call them. Zero-cost trial.
L2 · Serious: Expose the entire 'Data Operation Layer'
Go through all CRUD methods in the Repository layer—query, create, update, delete.
An Agent-friendly App is essentially one that exposes its Domain Model externally in the form of functions.
If a user tells Gemini "Share my trip to Tokyo last week with my colleague", and you haven't exposed getTrip, shareTrip, Gemini can't call them.
L3 · Aggressive: Refactor Information Architecture
If you believe Agents are one of the main entry points of the future, then the App's information architecture itself should be optimized for Agents.
Define all core operations first in the form of functions; UI is just one way to display these functions.
This is the exact opposite of the 'UI-driven development' order many teams currently follow.
Most teams starting from L1 is enough; L3 is for companies betting on AI Agents.
7. Finally
AppFunctions is not just Android adding an API; it's Android defining the first 'Interface Standard' for Apps in the Agent era:
- For Google: Gemini's capability radius expands from "answering questions" to "using all the Apps on your phone to do things for you", something Assistant never managed to do;
- For App Developers: A new user reach path is added—users no longer need to open your App, just ask Gemini. But conversely, Apps that haven't integrated AppFunctions are invisible to Gemini, and won't be used in voice scenarios;
- For the entire ecosystem: On the iOS side, there's Apple Intelligence's App Intents (a similar thing), both major platforms are moving in the same direction—from 'Humans operating Apps' to 'AI operating Apps'.
Four Paradigm Shifts in the Past 15 Years
- 1: Early 2010s, Push Notifications determined whether an App could retain users;
- 2: Mid 2010s, Deep Links + Sharing determined whether an App could be distributed by ecosystem traffic;
- 3: Early 2020s, Widgets + Live Activities determined an App's presence on the lock screen and home screen;
- 4: Mid 2020s, AppFunctions / App Intents determine whether an App can be 'discovered' and 'called' within the AI Agent ecosystem.
Each time it's a threshold of 'integration protocol', each time it's a watershed of 'ecosystem niche competition'.
Apps that haven't integrated the protocol are being left behind by the next generation of interaction paradigms.
📎 References
- AppFunctions Official Documentation: https://developer.android.com/ai/appfunctions
- Build intelligent Android apps with AppFunctions (Official Blog Part 4): https://android-developers.googleblog.com/2026/07/build-intelligent-android-apps-appfunctions.html