Skip to main content
Glama

android-app

Manage Android applications on devices: install, uninstall, start, stop, clear data, list packages, and retrieve app information through DroidMind MCP server.

Instructions

Perform various application management operations on an Android device.

This single tool consolidates various app-related actions. The 'action' parameter determines the operation.

Args: serial: Device serial number. action: The specific app operation to perform. ctx: MCP Context for logging and interaction. package (Optional[str]): Package name for the target application. Required by most actions. apk_path (Optional[str]): Path to the APK file (local to the server). Used by install_app. reinstall (Optional[bool]): Whether to reinstall if app exists. Used by install_app. grant_permissions (Optional[bool]): Whether to grant all requested permissions. Used by install_app. keep_data (Optional[bool]): Whether to keep app data and cache directories. Used by uninstall_app. activity (Optional[str]): Optional activity name to start. Used by start_app. extras (Optional[dict[str, str]]): Optional intent extras. Used by start_intent. include_system_apps (Optional[bool]): Whether to include system apps. Used by list_packages. include_app_name (Optional[bool]): Whether to include app labels (best-effort). Used by list_packages. include_apk_path (Optional[bool]): Whether to include APK paths. Used by list_packages. max_packages (Optional[int]): Max packages to return. Used by list_packages.

Returns: A string message indicating the result or status of the operation.


Available Actions and their specific argument usage:

  1. action="install_app"

    • Requires: apk_path

    • Optional: reinstall, grant_permissions

  2. action="uninstall_app"

    • Requires: package

    • Optional: keep_data

  3. action="start_app"

    • Requires: package

    • Optional: activity 3b. action="start_intent"

    • Requires: package, activity

    • Optional: extras

  4. action="stop_app"

    • Requires: package

  5. action="clear_app_data"

    • Requires: package

  6. action="list_packages"

    • Optional: include_system_apps, include_app_name, include_apk_path, max_packages

  7. action="get_app_manifest"

    • Requires: package

  8. action="get_app_permissions"

    • Requires: package

  9. action="get_app_activities"

    • Requires: package

  10. action="get_app_info"

    • Requires: package


Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
serialYes
actionYes
packageNo
apk_pathNo
reinstallNo
grant_permissionsNo
keep_dataNo
activityNo
extrasNo
include_system_appsNo
include_app_nameNo
include_apk_pathNo
max_packagesNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • Main handler for 'android-app' tool. Decorated with @mcp.tool(name='android-app'). Handles multiple sub-actions via AppAction enum by dispatching to internal helper functions.
    @mcp.tool(name="android-app")
    async def app_operations(
        # pylint: disable=too-many-arguments
        serial: str,
        action: AppAction,
        ctx: Context,
        package: str | None = None,  # Required for most actions
        apk_path: str | None = None,  # For install_app
        reinstall: bool = False,  # For install_app
        grant_permissions: bool = True,  # For install_app
        keep_data: bool = False,  # For uninstall_app
        activity: str = "",  # For start_app
        extras: dict[str, str] | None = None,  # For start_intent
        include_system_apps: bool = False,  # For list_packages
        include_app_name: bool = False,  # For list_packages
        include_apk_path: bool = True,  # For list_packages
        max_packages: int | None = 200,  # For list_packages
    ) -> str:
        """
        Perform various application management operations on an Android device.
    
        This single tool consolidates various app-related actions.
        The 'action' parameter determines the operation.
    
        Args:
            serial: Device serial number.
            action: The specific app operation to perform.
            ctx: MCP Context for logging and interaction.
            package (Optional[str]): Package name for the target application. Required by most actions.
            apk_path (Optional[str]): Path to the APK file (local to the server). Used by `install_app`.
            reinstall (Optional[bool]): Whether to reinstall if app exists. Used by `install_app`.
            grant_permissions (Optional[bool]): Whether to grant all requested permissions. Used by `install_app`.
            keep_data (Optional[bool]): Whether to keep app data and cache directories. Used by `uninstall_app`.
            activity (Optional[str]): Optional activity name to start. Used by `start_app`.
            extras (Optional[dict[str, str]]): Optional intent extras. Used by `start_intent`.
            include_system_apps (Optional[bool]): Whether to include system apps. Used by `list_packages`.
            include_app_name (Optional[bool]): Whether to include app labels (best-effort). Used by `list_packages`.
            include_apk_path (Optional[bool]): Whether to include APK paths. Used by `list_packages`.
            max_packages (Optional[int]): Max packages to return. Used by `list_packages`.
    
        Returns:
            A string message indicating the result or status of the operation.
    
        ---
        Available Actions and their specific argument usage:
    
        1.  `action="install_app"`
            - Requires: `apk_path`
            - Optional: `reinstall`, `grant_permissions`
        2.  `action="uninstall_app"`
            - Requires: `package`
            - Optional: `keep_data`
        3.  `action="start_app"`
            - Requires: `package`
            - Optional: `activity`
        3b. `action="start_intent"`
            - Requires: `package`, `activity`
            - Optional: `extras`
        4.  `action="stop_app"`
            - Requires: `package`
        5.  `action="clear_app_data"`
            - Requires: `package`
        6.  `action="list_packages"`
            - Optional: `include_system_apps`, `include_app_name`, `include_apk_path`, `max_packages`
        7.  `action="get_app_manifest"`
            - Requires: `package`
        8.  `action="get_app_permissions"`
            - Requires: `package`
        9.  `action="get_app_activities"`
            - Requires: `package`
        10. `action="get_app_info"`
            - Requires: `package`
        ---
        """
        try:
            # Basic argument checks
            if (
                action
                in [
                    AppAction.UNINSTALL_APP,
                    AppAction.START_APP,
                    AppAction.START_INTENT,
                    AppAction.STOP_APP,
                    AppAction.CLEAR_APP_DATA,
                    AppAction.GET_APP_MANIFEST,
                    AppAction.GET_APP_PERMISSIONS,
                    AppAction.GET_APP_ACTIVITIES,
                    AppAction.GET_APP_INFO,
                ]
                and package is None
            ):
                return f"❌ Error: 'package' is required for action '{action.value}'."
    
            if action == AppAction.INSTALL_APP and apk_path is None:
                return "❌ Error: 'apk_path' is required for action 'install_app'."
    
            if action == AppAction.START_INTENT and not activity:
                return "❌ Error: 'activity' is required for action 'start_intent'."
    
            # Dispatch to implementations
            if action == AppAction.INSTALL_APP:
                # We already checked apk_path is not None
                return await _install_app_impl(serial, apk_path, ctx, reinstall, grant_permissions)  # type: ignore
            if action == AppAction.UNINSTALL_APP:
                return await _uninstall_app_impl(serial, package, ctx, keep_data)  # type: ignore
            if action == AppAction.START_APP:
                return await _start_app_impl(serial, package, ctx, activity)  # type: ignore
            if action == AppAction.START_INTENT:
                assert package is not None
                return await start_intent(
                    serial=serial,
                    package=package,
                    activity=activity,
                    ctx=ctx,
                    extras=extras,
                    device_manager=get_device_manager(),
                )  # type: ignore[arg-type]
            if action == AppAction.STOP_APP:
                return await _stop_app_impl(serial, package, ctx)  # type: ignore
            if action == AppAction.CLEAR_APP_DATA:
                return await _clear_app_data_impl(serial, package, ctx)  # type: ignore
            if action == AppAction.LIST_PACKAGES:
                return await _list_packages_impl(
                    serial,
                    ctx,
                    include_system_apps=include_system_apps,
                    include_app_name=include_app_name,
                    include_apk_path=include_apk_path,
                    max_packages=max_packages,
                )
            if action == AppAction.GET_APP_MANIFEST:
                return await _get_app_manifest_impl(serial, package, ctx)  # type: ignore
            if action == AppAction.GET_APP_PERMISSIONS:
                return await _get_app_permissions_impl(serial, package, ctx)  # type: ignore
            if action == AppAction.GET_APP_ACTIVITIES:
                return await _get_app_activities_impl(serial, package, ctx)  # type: ignore
            if action == AppAction.GET_APP_INFO:
                return await _get_app_info_impl(serial, package, ctx)  # type: ignore
    
            # Should not be reached if AppAction enum is comprehensive
            valid_actions = ", ".join([act.value for act in AppAction])
            logger.error("Invalid app action '%s' received. Valid actions are: %s", action, valid_actions)
            return f"❌ Error: Unknown app action '{action}'. Valid actions are: {valid_actions}."
    
        except Exception as e:
            logger.exception(
                "Unexpected error during app operation %s on %s for package '%s': %s", action, serial, package, e
            )
            return f"❌ Error: An unexpected error occurred during '{action.value}': {e!s}"
  • AppAction Enum: Defines all sub-actions (install_app, uninstall_app, etc.) for the android-app tool, used as the 'action' parameter.
    class AppAction(str, Enum):
        """Defines the available sub-actions for the 'android-app' tool."""
    
        INSTALL_APP = "install_app"
        UNINSTALL_APP = "uninstall_app"
        START_APP = "start_app"
        START_INTENT = "start_intent"
        STOP_APP = "stop_app"
        CLEAR_APP_DATA = "clear_app_data"
        LIST_PACKAGES = "list_packages"
        GET_APP_MANIFEST = "get_app_manifest"
        GET_APP_PERMISSIONS = "get_app_permissions"
        GET_APP_ACTIVITIES = "get_app_activities"
        GET_APP_INFO = "get_app_info"
  • PackageInfo dataclass: Type definition for extracted package metadata like version, paths, etc.
    @dataclass
    class PackageInfo:
        """Basic package information."""
    
        version_code: str | None = None
        version_name: str | None = None
        min_sdk: str | None = None
        target_sdk: str | None = None
        install_path: str = "Unknown"
        first_install: str = "Unknown"
        last_update: str | None = None
        user_id: str = "Unknown"
        cpu_arch: str | None = None
        data_dir: str | None = None
        flags: str | None = None
  • AppAnalyzer class: Static methods for parsing dumpsys package output to extract and format manifest info, permissions, components (activities, services, etc.).
    class AppAnalyzer:
        """Class for analyzing and extracting app information."""
    
        @staticmethod
        def extract_package_info(dump_output: str) -> PackageInfo:
            """Extract basic package information from dumpsys output."""
            info = PackageInfo()
    
            # Version info
            if match := re.search(r"versionCode=(\d+)", dump_output):
                info.version_code = match.group(1)
            if match := re.search(r"versionName=([^\s]+)", dump_output):
                info.version_name = match.group(1)
            if match := re.search(r"minSdk=(\d+)", dump_output):
                info.min_sdk = match.group(1)
            if match := re.search(r"targetSdk=(\d+)", dump_output):
                info.target_sdk = match.group(1)
    
            # Paths and IDs
            if match := re.search(r"codePath=([^\s]+)", dump_output):
                info.install_path = match.group(1)
            if match := re.search(r"firstInstallTime=([^\r\n]+)", dump_output):
                info.first_install = match.group(1)
            if match := re.search(r"lastUpdateTime=([^\r\n]+)", dump_output):
                info.last_update = match.group(1)
            if match := re.search(r"userId=(\d+)", dump_output):
                info.user_id = match.group(1)
    
            # System info
            if match := re.search(r"primaryCpuAbi=([^\s]+)", dump_output):
                if match.group(1) != "null":
                    info.cpu_arch = match.group(1)
            if match := re.search(r"dataDir=([^\s]+)", dump_output):
                info.data_dir = match.group(1)
            if match := re.search(r"flags=\[\s([^\]]+)\s\]", dump_output):
                info.flags = match.group(1)
    
            return info
    
        @staticmethod
        def format_package_info(info: PackageInfo) -> str:
            """Format package information as markdown."""
            manifest = "## Package Information\n\n"
    
            # Version info
            if any([info.version_code, info.version_name, info.min_sdk, info.target_sdk]):
                if info.version_code:
                    manifest += f"- **Version Code**: {info.version_code}\n"
                if info.version_name:
                    manifest += f"- **Version Name**: {info.version_name}\n"
                if info.min_sdk:
                    manifest += f"- **Min SDK**: {info.min_sdk}\n"
                if info.target_sdk:
                    manifest += f"- **Target SDK**: {info.target_sdk}\n"
    
            # Paths and dates
            manifest += f"- **Install Path**: {info.install_path}\n"
            manifest += f"- **First Install**: {info.first_install}\n"
            if info.last_update:
                manifest += f"- **Last Update**: {info.last_update}\n"
            manifest += f"- **User ID**: {info.user_id}\n"
    
            # Optional system info
            if info.cpu_arch:
                manifest += f"- **CPU Architecture**: {info.cpu_arch}\n"
            if info.data_dir:
                manifest += f"- **Data Directory**: {info.data_dir}\n"
            if info.flags:
                manifest += f"- **Flags**: {info.flags}\n"
    
            return manifest
    
        @staticmethod
        def extract_permissions(dump_output: str) -> tuple[list[str], list[str]]:
            """Extract declared and requested permissions from dumpsys output."""
            declared_perms = []
            requested_perms = []
    
            # Extract declared permissions
            if declared_match := re.compile(r"declared permissions:\s*\r?\n((?:\s+[^\r\n]+\r?\n)+)", re.MULTILINE).search(
                dump_output
            ):
                declared_block = declared_match.group(1)
                for line in declared_block.split("\n"):
                    if perm_match := re.match(r"\s+([^:]+):", line.strip()):
                        declared_perms.append(perm_match.group(1))
    
            # Extract requested permissions
            if requested_match := re.compile(r"requested permissions:\s*\r?\n((?:\s+[^\r\n]+\r?\n)+)", re.MULTILINE).search(
                dump_output
            ):
                requested_block = requested_match.group(1)
                requested_perms = [line.strip() for line in requested_block.split("\n") if line.strip()]
    
            return declared_perms, requested_perms
    
        @staticmethod
        def format_permissions(declared_perms: list[str], requested_perms: list[str]) -> str:
            """Format permissions as markdown."""
            manifest = "\n## Permissions\n\n"
    
            # Declared permissions
            manifest += "### Declared Permissions\n\n"
            if declared_perms:
                for perm in declared_perms:
                    manifest += f"- `{perm}`\n"
            else:
                manifest += "No declared permissions.\n"
    
            # Requested permissions
            manifest += "\n### Requested Permissions\n\n"
            if requested_perms:
                for perm in requested_perms:
                    manifest += f"- `{perm}`\n"
            else:
                manifest += "No requested permissions.\n"
    
            return manifest
    
        @staticmethod
        def extract_components(dump_output: str, package: str) -> tuple[list[str], list[str], list[str], list[str]]:
            """Extract activities, services, providers, and receivers from dumpsys output."""
            activities = []
            services = []
            providers = []
            receivers = []
    
            # Extract activities
            activity_pattern = re.compile(r"([a-zA-Z0-9_$.\/]+/[a-zA-Z0-9_$.]+) filter", re.MULTILINE)
            main_activity_pattern = re.compile(r"([a-zA-Z0-9_$.]+/\.[a-zA-Z0-9_$.]+) filter", re.MULTILINE)
    
            for match in activity_pattern.finditer(dump_output):
                activity = match.group(1)
                if activity not in activities and activity.startswith(f"{package}/"):
                    activities.append(activity)
    
            for match in main_activity_pattern.finditer(dump_output):
                activity = match.group(1)
                if activity not in activities and activity.startswith(f"{package}/"):
                    activities.append(activity)
    
            # Extract services
            if service_section_match := re.search(
                r"Service Resolver Table:(.*?)(?:\r?\n\r?\n|\r?\nProvider Resolver Table:)", dump_output, re.DOTALL
            ):
                service_section = service_section_match.group(1)
                service_pattern = re.compile(r"([a-zA-Z0-9_$.\/]+/[a-zA-Z0-9_$.]+)", re.MULTILINE)
                for match in service_pattern.finditer(service_section):
                    service = match.group(1)
                    if service not in services and service.startswith(f"{package}/"):
                        services.append(service)
    
            # Extract providers
            if provider_section_match := re.search(
                r"Provider Resolver Table:(.*?)(?:\r?\n\r?\n|\r?\nReceiver Resolver Table:)", dump_output, re.DOTALL
            ):
                provider_section = provider_section_match.group(1)
                provider_pattern = re.compile(r"([a-zA-Z0-9_$.\/]+/[a-zA-Z0-9_$.]+)", re.MULTILINE)
                for match in provider_pattern.finditer(provider_section):
                    provider = match.group(1)
                    if provider not in providers and provider.startswith(f"{package}/"):
                        providers.append(provider)
    
            # Extract receivers
            if receiver_section_match := re.search(
                r"Receiver Resolver Table:(.*?)(?:\r?\n\r?\n|\r?\nService Resolver Table:)", dump_output, re.DOTALL
            ):
                receiver_section = receiver_section_match.group(1)
                receiver_pattern = re.compile(r"([a-zA-Z0-9_$.\/]+/[a-zA-Z0-9_$.]+)", re.MULTILINE)
                for match in receiver_pattern.finditer(receiver_section):
                    receiver = match.group(1)
                    if receiver not in receivers and receiver.startswith(f"{package}/"):
                        receivers.append(receiver)
    
            return activities, services, providers, receivers
    
        @staticmethod
        def get_intent_filters(component: str, dump_output: str) -> list[str]:
            """Extract intent filters for a component."""
            component_short = component.split("/")[-1]
            filters = []
            in_filter = False
    
            for line in dump_output.splitlines():
                if component_short in line and "filter" in line:
                    in_filter = True
                    continue
                if in_filter:
                    if not line.strip() or line.startswith("      Filter"):
                        continue
                    if not line.startswith(" " * 10):
                        in_filter = False
                        break
                    filter_info = line.strip()
                    if filter_info and filter_info not in filters:
                        filters.append(filter_info)
    
            return filters
    
        @staticmethod
        def format_component_section(title: str, components: list[str], dump_output: str) -> str:
            """Format a component section as markdown."""
            manifest = f"\n### {title}\n\n"
    
            if not components:
                manifest += f"No {title.lower()} found.\n"
                return manifest
    
            for component in components:
                manifest += f"- `{component}`\n"
                filters = AppAnalyzer.get_intent_filters(component, dump_output)
                if filters:
                    manifest += "  Intent Filters:\n"
                    for f in filters:
                        manifest += f"  - {f}\n"
    
            return manifest
    
        @staticmethod
        def format_components(
            activities: list[str], services: list[str], providers: list[str], receivers: list[str], dump_output: str
        ) -> str:
            """Format all components as markdown."""
            manifest = "\n## Components\n"
            manifest += AppAnalyzer.format_component_section("Activities", activities, dump_output)
            manifest += AppAnalyzer.format_component_section("Services", services, dump_output)
            manifest += AppAnalyzer.format_component_section("Content Providers", providers, dump_output)
            manifest += AppAnalyzer.format_component_section("Broadcast Receivers", receivers, dump_output)
            return manifest
  • Tool registration decorator @mcp.tool(name='android-app') applied to the handler function.
    @mcp.tool(name="android-app")
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It does an excellent job explaining the action-specific parameter requirements and optional parameters through the detailed 'Available Actions' section. It clarifies which parameters are required for each action and provides context about parameter usage (e.g., 'Used by `install_app`'). However, it doesn't mention authentication needs, rate limits, or error conditions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with clear sections (purpose, args, returns, action details). While somewhat lengthy due to the many parameters and actions, every sentence earns its place by providing essential information. The front-loaded purpose statement is clear, though the detailed action list could be better integrated.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (13 parameters, 11 actions) and 0% schema description coverage, the description provides excellent coverage. It explains all parameters, maps them to actions, and clarifies requirements. With an output schema present, it doesn't need to explain return values. The main gap is lack of sibling tool differentiation and some behavioral context like error handling.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0% schema description coverage, the description must fully compensate, and it does so exceptionally well. The 'Args' section provides clear explanations for each parameter, and the 'Available Actions' section maps parameters to specific actions with required/optional status. This adds substantial meaning beyond what the bare schema provides, making parameter usage completely clear.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose3/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states 'Perform various application management operations on an Android device' which is a clear purpose, but it's somewhat vague about what specific operations are available until the detailed list later. It distinguishes itself from siblings by focusing on app management rather than device control, diagnostics, files, etc., but this differentiation isn't explicitly stated in the opening description.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus its sibling tools (android-device, android-file, etc.). While it explains that different actions require different parameters, it doesn't offer context about when to choose specific actions or what alternatives might exist in other tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/hyperb1iss/droidmind'

If you have feedback or need assistance with the MCP directory API, please join our Discord server