Skip to main content
Glama

android-diag

Capture bug reports and dump heap memory from Android devices to diagnose app performance issues and system errors.

Instructions

Perform diagnostic operations like capturing bug reports or heap dumps.

Args: ctx: MCP Context. serial: Device serial number. action: The diagnostic action to perform. output_path: Optional. Path to save the output file. For bugreport: host path for adb to write the .zip. If empty, a temp file is used & summarized. For dump_heap: local path to save the .hprof. If empty, a temp file is used. include_screenshots: For CAPTURE_BUGREPORT. Default True. package_or_pid: For DUMP_HEAP. App package name or process ID. native: For DUMP_HEAP. True for native (C/C++) heap, False for Java. Default False. timeout_seconds: Max time for the operation. If 0, action-specific defaults are used (bugreport: 300s, dump_heap: 120s).

Returns: A string message indicating the result or path to the output.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
serialYes
actionYes
output_pathNo
include_screenshotsNo
package_or_pidNo
nativeNo
timeout_secondsNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • The primary handler function for the 'android-diag' tool. It is decorated with @mcp.tool(name='android-diag') and implements the core logic by dispatching to specific diagnostic actions (bugreport capture or heap dump) based on the 'action' parameter.
    @mcp.tool(name="android-diag")
    async def android_diag(
        ctx: Context,
        serial: str,
        action: DiagAction,
        output_path: str = "",
        include_screenshots: bool = True,
        package_or_pid: str | None = None,
        native: bool = False,
        timeout_seconds: int = 0,
    ) -> str:
        """
        Perform diagnostic operations like capturing bug reports or heap dumps.
    
        Args:
            ctx: MCP Context.
            serial: Device serial number.
            action: The diagnostic action to perform.
            output_path: Optional. Path to save the output file.
                         For bugreport: host path for adb to write the .zip. If empty, a temp file is used & summarized.
                         For dump_heap: local path to save the .hprof. If empty, a temp file is used.
            include_screenshots: For CAPTURE_BUGREPORT. Default True.
            package_or_pid: For DUMP_HEAP. App package name or process ID.
            native: For DUMP_HEAP. True for native (C/C++) heap, False for Java. Default False.
            timeout_seconds: Max time for the operation. If 0, action-specific defaults are used
                             (bugreport: 300s, dump_heap: 120s).
    
        Returns:
            A string message indicating the result or path to the output.
        """
        if action == DiagAction.CAPTURE_BUGREPORT:
            final_timeout = timeout_seconds if timeout_seconds > 0 else 300
            return await _capture_bugreport_impl(
                serial=serial,
                ctx=ctx,
                output_path=output_path,
                include_screenshots=include_screenshots,
                timeout_seconds=final_timeout,
            )
        if action == DiagAction.DUMP_HEAP:
            if not package_or_pid:
                msg = "Error: 'package_or_pid' is required for dump_heap action."
                await ctx.error(msg)
                return msg
            final_timeout = timeout_seconds if timeout_seconds > 0 else 120
            return await _dump_heap_impl(
                serial=serial,
                ctx=ctx,
                package_or_pid=package_or_pid,
                output_path=output_path,
                native=native,
                timeout_seconds=final_timeout,
            )
    
        # Should not be reached
        unhandled_action_msg = f"Error: Unhandled diagnostic action '{action}'."
        logger.error(unhandled_action_msg)
        await ctx.error(unhandled_action_msg)
        return unhandled_action_msg
  • Enum defining the possible actions for the android-diag tool: CAPTURE_BUGREPORT and DUMP_HEAP. Used in the tool's input schema.
    class DiagAction(Enum):
        """Actions available for diagnostic operations."""
    
        CAPTURE_BUGREPORT = "capture_bugreport"
        DUMP_HEAP = "dump_heap"
  • Helper function implementing bug report capture logic, handling temporary or specified output paths, screenshots inclusion, and timeouts.
    async def _capture_bugreport_impl(
        serial: str, ctx: Context, output_path: str = "", include_screenshots: bool = True, timeout_seconds: int = 300
    ) -> str:
        """Implementation for capturing a bug report from a device."""
        try:
            device = await get_device_manager().get_device(serial)
            if not device:
                await ctx.error(f"Device {serial} not connected or not found.")
                return f"Error: Device {serial} not found."
    
            log_command_execution("capture_bugreport", RiskLevel.MEDIUM)
    
            await ctx.info(f"🔍 Capturing bug report from device {serial}...")
            await ctx.info("This may take a few minutes depending on the device's state.")
    
            if include_screenshots:
                await ctx.info("Including screenshots in the bug report.")
                bugreport_cmd_arg = "bugreport"
            else:
                await ctx.info("Excluding screenshots to reduce bug report size.")
                bugreport_cmd_arg = "bugreportz"
    
            if not output_path:
                # Scenario: Temporary file. This will use 'with' inside the helper.
                return await _execute_bugreport_temp(device, serial, ctx, bugreport_cmd_arg, timeout_seconds)
    
            # Scenario: User-specified file.
            # Ensure the output directory exists if a path is provided
            abs_output_path = os.path.abspath(output_path)
            os.makedirs(os.path.dirname(abs_output_path), exist_ok=True)
            return await _execute_bugreport_core(
                device, serial, ctx, bugreport_cmd_arg, abs_output_path, timeout_seconds, is_temporary=False
            )
    
        except Exception as e:
            logger.exception("Error capturing bug report: %s", e)
            await ctx.error(f"Error capturing bug report: {e}")
            return f"Error: {e}"
  • Helper function implementing heap dump capture, including PID resolution, native/java support, device-side dump, file pull, and cleanup.
    async def _dump_heap_impl(
        serial: str,
        ctx: Context,
        package_or_pid: str,
        output_path: str = "",
        native: bool = False,
        timeout_seconds: int = 120,
    ) -> str:
        """Implementation for capturing a heap dump from a running process on the device."""
        temp_dir_for_local_dump = None
        try:
            device = await get_device_manager().get_device(serial)
            if not device:
                await ctx.error(f"Device {serial} not connected or not found.")
                return f"Error: Device {serial} not found."
    
            log_command_execution("dump_heap", RiskLevel.MEDIUM)
    
            pid = await _resolve_pid(device, ctx, package_or_pid)
            if not pid:
                return f"Error: Cannot find process for '{package_or_pid}'. Make sure the app is running."
    
            app_name_for_file = package_or_pid if not package_or_pid.isdigit() else f"pid_{pid}"
            dump_type = "native" if native else "java"
    
            device_dump_path, local_final_path, temp_dir_for_local_dump = await _generate_heap_dump_paths(
                device, app_name_for_file, dump_type, output_path, ctx
            )
    
            await ctx.info(f"📊 Capturing {dump_type} heap dump for process {pid} ({package_or_pid})...")
            await ctx.info(f"Device path: {device_dump_path}, Local path: {local_final_path}")
            await ctx.info("This may take some time depending on the process size.")
    
            dump_cmd = f"am dumpheap {'-n ' if native else ''}{pid} {device_dump_path}"
    
            if native:
                is_root = "root" in (await device.run_shell("whoami")).lower()
                if not is_root:
                    await ctx.warning(
                        "Native heap dumps often require root access. The command will be attempted, "
                        "but may fail if root is not available or if SELinux policies prevent it."
                    )
    
            await ctx.info(f"Executing heap dump command on device: {dump_cmd}")
            try:
                # Execute the heap dump command
                dump_stdout = await asyncio.wait_for(device.run_shell(dump_cmd), timeout=timeout_seconds + 5)
    
                # Check if dump file was created on device
                # 'ls -d' checks existence of specific file/dir, returns itself if exists
                file_check_cmd = f"ls -d {device_dump_path}"
                file_check_result = await device.run_shell(file_check_cmd)
    
                if device_dump_path not in file_check_result:
                    await ctx.error(f"Heap dump file was not created on device at {device_dump_path}.")
                    await ctx.info(f"Output from dump command: {dump_stdout}")
                    return f"Failed to create heap dump on device. ADB command output: {dump_stdout or '(no output)'}"
    
                # Get file size (optional, for info)
                size_check_cmd = f"ls -l {device_dump_path} | awk '{{print $5}}'"
                file_size_str = (await device.run_shell(size_check_cmd)).strip()
                if file_size_str.isdigit():
                    file_size_mb = int(file_size_str) / (1024 * 1024)
                    await ctx.info(f"Heap dump created on device: {device_dump_path} ({file_size_mb:.2f} MB).")
                else:
                    await ctx.info(f"Heap dump created on device: {device_dump_path} (size unknown).")
    
                await ctx.info(f"Pulling heap dump from {device_dump_path} to {local_final_path}...")
                pull_success = await device.pull_file(device_dump_path, local_final_path)
    
                if not pull_success or not os.path.exists(local_final_path):
                    await ctx.error(f"Failed to pull heap dump from device to {local_final_path}.")
                    return f"Failed to retrieve heap dump from device path {device_dump_path}."
    
                local_size_mb = os.path.getsize(local_final_path) / (1024 * 1024)
                await ctx.info(
                    f"✅ Heap dump captured successfully to {local_final_path}! File size: {local_size_mb:.2f} MB"
                )
    
                result_message_parts = [
                    f"{'Native' if native else 'Java'} heap dump saved to: `{local_final_path}` ({local_size_mb:.2f} MB)",
                    "",
                    "To analyze this heap dump:",
                ]
                if native:
                    result_message_parts.extend(
                        [
                            "1. Use Android Studio Memory Profiler (File > Open).",
                            "2. Or use `pprof` (common for native C++ dumps) or Google's `heapprof` tools.",
                        ]
                    )
                else:
                    result_message_parts.extend(
                        [
                            (
                                f'1. Convert using `hprof-conv "{local_final_path}" '
                                f'"converted_{os.path.basename(local_final_path)}"`.'
                            ),
                            "2. Open the converted file in Android Studio Memory Profiler or Eclipse MAT.",
                        ]
                    )
                return "\\n".join(result_message_parts)
    
            except TimeoutError:
                await ctx.error(f"Heap dump command timed out after {timeout_seconds} seconds on device.")
                return (
                    f"Heap dump capture timed out after {timeout_seconds} seconds during device-side operation. "
                    "Try with a longer timeout or ensure the app is stable."
                )
            finally:
                # Clean up the file on the device regardless of pull success to free space
                if device_dump_path and await device.file_exists(device_dump_path):
                    await ctx.info(f"Cleaning up temporary file from device: {device_dump_path}")
                    await device.run_shell(f"rm {device_dump_path}")
                if temp_dir_for_local_dump:
                    temp_dir_for_local_dump.cleanup()
    
        except Exception as e:
            logger.exception("Error dumping heap: %s", e)
            await ctx.error(f"Error dumping heap: {e!s}")
            if temp_dir_for_local_dump:
                temp_dir_for_local_dump.cleanup()
            return f"Error: {e!s}"
  • The @mcp.tool decorator registers the 'android-diag' tool with the MCP server.
    @mcp.tool(name="android-diag")
Behavior3/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 adds some context about temp file usage and timeouts, but doesn't cover critical aspects like whether this requires device permissions, if it's destructive to device state, error handling, or rate limits. The information is helpful but incomplete for a diagnostic tool.

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 a clear opening statement followed by organized parameter explanations. It's appropriately sized for a 7-parameter tool, though some sentences could be more front-loaded (e.g., the opening could more directly state the two main actions). Overall, it's efficient with minimal waste.

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 complexity (7 parameters, diagnostic operations), no annotations, and an output schema present, the description does a good job covering parameter semantics and basic behavior. However, it lacks context about when to use the tool versus siblings and doesn't fully address behavioral aspects like permissions or side effects, keeping it from a perfect score.

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 fully compensates by providing detailed explanations for all 7 parameters. It clarifies the purpose of each parameter, action-specific behaviors, defaults, and usage notes (e.g., temp file fallbacks, action-specific timeouts), adding significant value beyond the bare schema.

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

Purpose4/5

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

The description clearly states the tool performs diagnostic operations like capturing bug reports or heap dumps, providing specific verbs and resources. However, it doesn't explicitly differentiate from sibling tools like android-log or android-screenshot, which may also involve diagnostic functions, so it doesn't reach the highest score.

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 alternatives like android-log for logs or android-screenshot for screenshots. It lists parameters but doesn't explain the context or prerequisites for invoking diagnostic operations, leaving the agent to guess when this is appropriate.

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