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

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")

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