Android AppFunctions and On-Device MCP for Agents

AndroidAI AgentsAppFunctionsOn-Device AI

Agents are moving on-device, and the interesting question for app developers isn't "which model" — it's "how does an assistant actually do something in my app?" On Android the answer is increasingly AppFunctions: typed, annotated functions you expose so the system's agent (Gemini) can discover and call them with real arguments. If you've built an MCP server, the mental model transfers directly — your app becomes the local, on-device equivalent, letting an agent invoke capabilities without scraping your UI or making a network hop.

I've been wiring apps into assistant flows since the App Actions days, and AppFunctions is the first version of this that feels built for real agents rather than a fixed catalog of voice commands. Here's how it fits together and how to expose functionality without opening a security hole.

From App Actions to AppFunctions

The old model — built-in intents, shortcuts, App Actions — worked by mapping user phrases to a fixed set of system-defined intents. It was rigid: if Google hadn't defined an intent for your domain, you were stuck bending your feature into "ORDER_MENU_ITEM" or similar. That doesn't scale to agents that need to compose arbitrary steps.

AppFunctions flips it. You declare functions with schemas — inputs, outputs, descriptions — and the platform indexes them so an on-device agent can plan over them. The shift is from "here are the phrases I recognize" to "here are the typed capabilities I offer, you figure out when to use them." That's exactly the structured outputs and function calling pattern, moved on-device and into the OS.

Declaring an AppFunction

The API centers on the @AppFunction annotation over a suspend function. The parameters and return type become the schema the agent sees:

class ChargingFunctions(private val repo: ChargeRepository) {

    @AppFunction(
        description = "Start a charging session at a given charger for the signed-in user."
    )
    suspend fun startCharge(
        @AppFunctionParam(description = "Charger ID from a nearby list")
        chargerId: String,
        @AppFunctionParam(description = "Target charge percent, 50-100")
        targetPercent: Int = 80,
    ): ChargeSession {
        require(targetPercent in 50..100) { "targetPercent out of range" }
        return repo.startSession(chargerId, targetPercent)
    }
}

The compiler generates the metadata that registers this with the system's function index. The agent can now say, in effect, "the user asked to charge to 90% at the charger they're looking at" and produce a concrete startCharge("CHG-4821", 90) call. Rich return types get serialized into a schema too, so the agent can chain — call findNearbyChargers, pick one, then startCharge.

Why on-device matters here

Running this locally isn't just a latency win, though a call that never leaves the device does feel instant. The bigger deal is privacy and availability. The arguments the agent extracts — a receipt query, a charge target, a message body — stay on the phone. That aligns with the broader push toward on-device AI for privacy and pairs naturally with Gemini Nano doing the reasoning. No server means the capability works on a plane, and it means you're not shipping user intent to a backend just to open a screen.

Treat every function like a public API endpoint

This is where I get insistent, because it's the part that bites teams. An AppFunction is a remotely-triggerable entry point into your app. The agent calling it is trusted-ish, but the arguments originate from natural language the user (or a shared context) supplied. Guard accordingly:

A quick checklist I run before shipping any function:

Check Why
Argument validation Args come from an LLM's interpretation, not your UI
Ownership/auth enforced in-function The agent isn't your authorization layer
Idempotency on writes The agent may retry; don't double-charge
Confirmation for side effects Users must approve money/data actions
Minimal surface Only expose functions you'd expose as an API

Designing functions agents can actually use

The difference between a function that gets called correctly and one that confuses the planner is mostly in the descriptions and the granularity. Write descriptions the way you'd write API docs for a junior engineer: say what it does, what the arguments mean, and any constraints. Keep functions single-purpose — findNearbyChargers and startCharge compose better than one mega-function with a mode flag. And return structured, self-describing results so the agent can decide the next step without guessing.

The reliability lessons from server-side agents apply directly here; the same discipline behind building reliable AI agents — narrow tools, strict schemas, idempotent writes — is what makes on-device function calling dependable.

AppFunctions is early, and the API surface is still settling, but the direction is clear: the apps that get invoked by the phone's agent will be the ones that exposed clean, safe, well-described functions. That's a design problem more than an AI problem, and it's squarely in the wheelhouse of engineers who already think in terms of APIs and contracts.

Resources

Making an Android app assistant-ready? Reach out — happy to review your function surface.

Frequently asked questions

What are Android AppFunctions?

AppFunctions are typed, annotated Kotlin functions your app exposes to the system so an on-device agent like Gemini can discover and invoke them. Think of them as structured entry points — 'start a charge session', 'find a receipt' — that an assistant can call with real arguments rather than screen-scraping your UI.

How do AppFunctions relate to MCP?

Conceptually they solve the same problem MCP solves for servers: exposing typed capabilities to an agent. AppFunctions is Android's on-device, intent-style mechanism, so the app itself becomes the equivalent of a local MCP server that the platform agent can call without a network round-trip.

Do AppFunctions replace App Actions and App Intents?

They're the evolution of that lineage. App Actions and shortcuts were built around fixed built-in intents; AppFunctions let you declare arbitrary typed functions with schemas, which is far more flexible for agentic use cases where the assistant composes multiple calls.

Hiring a senior Android / Flutter engineer?

I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.

Get in touch →