Skip to main content
Glama

android-diag

Capture bug reports or dump heap data from Android devices using this diagnostic tool. Specify output paths, include screenshots, and set timeouts for efficient device analysis.

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
actionYes
include_screenshotsNo
nativeNo
output_pathNo
package_or_pidNo
serialYes
timeout_secondsNo

Implementation Reference

  • Main handler function for the 'android-diag' tool. Dispatches to 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 input schema for the 'action' parameter: CAPTURE_BUGREPORT or DUMP_HEAP.
    class DiagAction(Enum): """Actions available for diagnostic operations.""" CAPTURE_BUGREPORT = "capture_bugreport" DUMP_HEAP = "dump_heap"
  • Core helper for capturing bug reports from Android devices, handling temporary or specified output paths.
    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}"
  • Core helper for capturing Java or native heap dumps from processes on Android devices, including PID resolution and file pulling.
    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}"
  • MCP tool registration decorator for the android-diag tool.
    @mcp.tool(name="android-diag")

Other Tools

Related 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