AppFunctions の品質に関するガイドライン

AppFunctions は、Android の MCP サーバーに相当する実用的な機能です。これにより、アプリのロジックの一部を AI エージェントやアシスタントと共有できます。

エージェントは、説明を使用して、リクエストを処理する AppFunction を選択します。この選択プロセスは、従来の MCP ツール検出と同様に機能します。AI アプリケーションは、MCP サーバーとツールの説明を使用して選択をガイドします。アプリは Android MCP サーバーとして機能し、各 AppFunction はそのツールの 1 つとして機能します。次の説明を入力します。

  • アプリの説明(Android MCP サーバー)。アプリの説明は、エージェントがアプリの機能を理解するための最初の入り口です。エージェントがアプリを見つけやすくするには、アプリの主なドメインを先頭に置き、AppFunctions で実行できるタスクを簡潔にリストします。
    • アプリのメタデータ XML ファイルに、アプリの説明を自分で記述します。
    • アプリの説明を使用して、関数を呼び出す順序など、個々の関数の説明では説明できないことを説明します。
  • AppFunction の説明(ツール)。各関数記述は、1 つの AppFunction の動作を説明します。関数の説明は、一般的に、update、create、book などの動詞と、settings、channel、room などのリソースを中心に構成されます。

関数の説明に関するガイドライン

エージェントは、関数の説明に基づいて、リクエストに適合する関数と、その関数の呼び出し方法を決定します。

簡潔で明確な AppFunction の説明を記述する

説明が明確であるほど、エージェントはリクエストに適した関数をより確実に選択できます。次の表に、関数の説明で行うべきことと避けるべきことを示します。

すべきこと すべきでないこと
関数の目的と実行結果を、明確かつ中立的な言葉で説明します。 説明をプロンプトとして使用しないでください。「必ず言うこと」や「ユーザーに尋ねること」などのコマンドは、関数の説明ではなく、エージェントの動作を指示するものです。
説明の冒頭に重要な情報を記載し、簡潔にまとめます。 「この関数は、~するために使用されます」などの不要な説明は追加しないでください。フィラーは、説明を長くするだけで、わかりやすくはしません。
パラメータや出力など、コードの動作を正確に反映します。たとえば、読み取り専用として記述された関数は、データの書き込みや削除を行うべきではありません。 誤解を招く名前は使用しないでください。たとえば、ユーザーデータを削除する関数に fetchData という名前を付けないでください。
パラメータや戻り値の型など、関数の説明を KDoc 形式で記述します。命名については Kotlin スタイルガイドに沿って、実装に関する知識がなくても理解できる説明にします。 説明の一部を強調するために、大文字や特殊文字(**CRITICAL** や VERY IMPORTANT など)を過度に使用しないでください。
AppFunction には、人が読める形式の名前を使用します。
各 AppFunction の機能を明確かつ直接的に説明します。 「最初に連絡先を検索する」など、他の機能を含む手順の説明は行わないでください。エージェントはアプリの説明を使用して関数間の呼び出しを計画するため、その順序をアプリの説明に記述します。
「X がある場合は Y」などの言語を使用して、エージェントが関数から期待できることを記述します。
エージェントには、AppFunctions の一般的な使用方法に関する独自の指示があるとします。関数の説明には、関数が何を行うかを記述します。エージェントの指示には、関数の使用方法を記述します。 関数の説明でエージェント レベルの指示をオーバーライドしたり、埋め込んだりしないでください。

カプセル化を念頭に置いて記述する

各関数の説明は、エージェントがその関数のみを使用するように、自己完結型にしてください。次の表に、カプセル化のガイドラインを示します。

すべきこと すべきでないこと
関数の説明は、明確に定義された単一の目的を持ち、具体的で焦点を絞ったものにします。(例: 「個人のタスクを作成、検索、削除する」)。 説明が広範すぎると、エージェントが不適切なタイミングや誤った理由で関数を呼び出す可能性があります。
多目的の AppFunction を作成したり、同じユースケースに複数の AppFunction を公開したりしないでください。複数の関数が類似したインテントを持つ場合、エージェントが誤った関数を選択する可能性が高くなります。
アプリ内または他のアプリ内の他の AppFunction を参照したり、その動作に影響を与えようとしたりしないでください。これにより、意図しない依存関係や副作用が生じる可能性があります。

許可されるアクションとデータ共有

エージェントがアプリの機能を使用する際にユーザーのプライバシーと安全を保護するには、次のガイドラインに沿ってください。

すべきこと すべきでないこと
ユーザーがアプリで通常行う安全で非破壊的なタスクのみを実行します。エージェントは、ユーザーのリクエストから明確なパラメータを提供できます。たとえば、タイトル付きのタスクの作成やメモの検索などです。
高度な個人情報や機密情報ではない情報、またはユーザーがアクションのために共有することに明示的に同意したデータのみを共有します。 エージェントにユーザーデータやシステム リソースへの広範なアクセス権を付与しないでください。
パスワード、財務情報、秘密鍵、その他の機密性の高い個人情報を必要とする、または返す AppFunction を設計しないでください。
ユーザーがリクエストしたアクションを、後で実行する場合やバックグラウンドで実行する場合でも実行する(ユーザーが選択した時刻にメッセージを送信するなど)。 ユーザーがリクエストしていないアクション(自動的に開始されるタスクや、リクエストの範囲を超えるタスクなど)は実行しないでください。
自然言語のメリットを享受できる機能に焦点を当てます。たとえば、UI ナビゲーションよりも会話で表現しやすいタスク(「[AI エージェント]、来週のカレンダーで空いている時間を見つけて、そのうちの 1 日にシカゴ行きのフライトを提案してください」など)です。
スマートフォン以外のフォーム ファクタでは、テレビ、Android Auto、Android Automotive OS などの共有デバイスでのユーザーのプライバシーを尊重します。 アプリの UI がそのフォーム ファクタでサポートしていない情報を開示したり、アクションを実行したりしないでください。
関数の目的を明確かつ一意に特定します。 別のツールやサービスを装わないでください。関数の名前と説明は、他のツールと混同しやすいものであってはなりません。エージェントが誤って関数を選択する可能性があります。

パラメータと戻り値の設計

パラメータと戻り値を慎重に設計することで、曖昧さを排除し、無効な呼び出しの可能性を減らすことができます。次の規則に従ってください。

すべきこと すべきでないこと
関数に必要なデータに対してのみパラメータを定義します。 関数が使用しない情報はリクエストしないでください。これにより、データの露出が最小限に抑えられ、関数の使用が簡素化されます。
想定される入力を反映したパラメータ名(taskId、searchQuery、recipientEmail など)を使用します。 full_context や conversation_history などの名前は使用しないでください。エージェントはこれらの用語を独自のコンテキストと会話履歴に使用するため、このような名前は曖昧です。エージェントがこれらを誤って解釈し、大量の不要なデータを関数に渡して、データの引き出しにつながる可能性があります。
独自のタイプを定義するのではなく、AppFunctions がサポートするタイプを使用します。たとえば、日付と時刻には LocalDate、LocalTime、LocalDateTime、Instant を、リンクには Uri を使用します。ライブラリは、これらのタイプをすべてのアプリで同じように表すため、エージェントは一貫した形式を取得します。サポートされているタイプの完全なリストについては、AppFunctionSerializable をご覧ください。
パラメータが関数の目的に不可欠でない場合は、省略可能にします。これにより、ユーザーから部分的な情報しか得られない場合でも、エージェントは柔軟に対応できます。詳細については、オプション パラメータをご覧ください。
AppFunction が既存のリソースを更新する場合は、各パラメータが「null に設定」と「変更しない」を区別できるようにします。例については、既存のリソースの更新をご覧ください。
エージェントがアプリで特定の画面を開けるようにするには、その画面の PendingIntent を返します。エージェントは、アプリのインテントを構築することなく、PendingIntent をそのまま起動できます。エージェントが誘導先を変更できないように、明示的なインテントと FLAG_IMMUTABLE を使用して作成します。

オプション パラメータ

省略可能にできるのは、null 値許容型、プリミティブ型、リスト型、配列型のみです。エージェントがオプション パラメータを省略すると、ライブラリは、宣言したデフォルト値ではなく、型のデフォルト値(null、ゼロ、false、空)を渡します。次の例のように、関数の本文で意味のあるデフォルト値を解決します。

/**
 * Creates a note.
 *
 * @param title The title of the note.
 * @param content The body text of the note.
 * @param attachments The files to attach to the note.
 * @param owner The ID of the user who owns the note.
 * @return The created note.
 */
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun createNote(
    title: String, // Required
    content: String? = null, // Optional
    attachments: List<Attachment> = emptyList(), // Optional
    owner: String? = null, // Optional
): Note = withContext(Dispatchers.IO) {
    noteRepository.createNote(
        title = title,
        // The library ignores declared default values and passes null for
        // omitted nullable parameters, so resolve meaningful defaults here.
        content = content ?: "",
        attachments = attachments,
        owner = owner ?: currentUserId(),
    )
}

既存のリソースの更新

次の例では、エージェントが更新できる各フィールドに、null 値を許容する SetField ラッパーを使用しています。SetField が null の場合は「変更しない」ことを意味し、SetField(null) は「null に設定する」ことを意味します。

/** A new value for a field of an existing item. */
@AppFunctionSerializable(isDescribedByKDoc = true)
data class SetField<T>(
    /** The new value of the field. */
    val value: T,
)

/**
 * Updates an existing note. Omitted fields keep their values.
 *
 * @param noteId The ID of the note to update.
 * @param title The new title of the note.
 * @param content The new body text of the note.
 * @return The updated note.
 */
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun updateNote(
    noteId: String,
    title: SetField<String>? = null,
    content: SetField<String?>? = null,
): Note = withContext(Dispatchers.IO) {
    val note = noteRepository.getNote(noteId)
        ?: throw AppFunctionElementNotFoundException(
            "No note found with ID $noteId"
        )
    noteRepository.updateNote(
        noteId = noteId,
        // A null SetField means "don't change", and SetField(null) means
        // "set to null", so don't use ?: for nullable fields.
        title = title?.value ?: note.title,
        content = if (content != null) content.value else note.content,
    )
}

エラー処理

呼び出しが失敗すると、エージェントは関数がスローした例外(タイプとメッセージを含む)を受け取ります。エージェントが障害から復旧したり、ユーザーに報告したりできるように、次の規則に従ってください。

すべきこと すべきでないこと
原因に一致する AppFunctionException サブクラスをスローし、問題をわかりやすい言葉で説明するメッセージを添えます。たとえば、引数が無効な場合は AppFunctionInvalidArgumentException、リクエストされたアイテムが存在しない場合は AppFunctionElementNotFoundException をスローします。タイプは、発生した障害の種類をエージェントに伝え、メッセージは、引数の修正やユーザーへの通知など、復元に必要な詳細をエージェントに伝えます。既存のリソースの更新の updateNote の例は、このパターンを示しています。 他の例外が関数からエスケープされないようにしてください。エージェントは、エラーの種類を特定しない AppFunctionAppUnknownException としてエラーを受け取ります。