android-debug-mcp
Android Debug MCP
中文 | English
Android Debug MCP 是一个本地 MCP server,用来让 AI agent 调试 Android debuggable app 的运行时状态。
它把屏幕查看、受控设备操作、JDWP/JDI attach、Java/Kotlin 行断点、变量读取、timeline 导出、断点证据记录等能力暴露成 MCP tools。
一句话原理
AI client 调用一个本地 Kotlin MCP server;server 通过 adb 操作设备,通过 JDWP/JDI attach 到 app,然后把断点、变量、屏幕和时序证据结构化返回给 AI。
为什么需要这个项目
AI 可以读源码,但 Android 运行时 bug 只看源码往往不够。真实问题通常依赖当前屏幕、进程、线程、调用栈、对象状态和事件顺序。
Android Debug MCP 的目标是给 AI 一条安全、可控、可复盘的运行时调试通道:
- AI 负责判断下一步调试策略。
- MCP server 负责可靠执行底层调试动作。
- 结论来自断点、变量、屏幕和 timeline 证据。
核心能力
- 列出已连接的 Android 设备。
- 获取截图、UIAutomator 树、屏幕尺寸和当前 Activity。
- 执行受控输入动作,例如 tap、swipe、text、back、launch,以及基于 UI 文本的点击。
- 通过
adb forward tcp:<port> jdwp:<pid>attach 到 debuggable Android app。 - 设置 Java/Kotlin class + line 断点,包括尚未加载的 class。
- 等待断点命中,并查看线程、调用栈、本地变量名、类名、方法名和行号。
- 从暂停的 stack frame 中读取指定变量和字段。
- 在多模块项目中把 Kotlin/JVM class name 解析回可能的源码文件。
- 记录调试会话 timeline,并导出
session.json/session.html。 - 按
File.kt:L123聚合断点证据,并保留全局命中顺序。 - 执行短距离 server-side debug plan,用于确定性的重复采集循环。
安全边界
- 只支持 Java/Kotlin 运行时调试。
- 不支持 native 或 C++ 调试。
- 不修改变量。
- 不任意调用 app 方法。
- 不执行任意
adb shell。 - 设备操作必须通过 allowlist 内的
action_executeaction。 - 断点变量读取只作用于当前暂停的事件。
这些限制是为了保证运行时证据可信。一个可以随意修改 app 状态的调试工具,很可能制造出它正在试图诊断的问题。
底层原理
flowchart LR
AI["AI Client"] --> MCP["MCP JSON-RPC over stdio"]
MCP --> Server["android-debug-mcp Kotlin Server"]
Server --> ADB["adb"]
ADB --> Device["Android Device"]
Server --> JDI["JDI VirtualMachine"]
JDI --> JDWP["adb forward tcp:port -> jdwp:pid"]
JDWP --> App["Debuggable App Process"]
Server --> Evidence["Timeline + Breakpoint Evidence"]
server 通过 stdio 使用 MCP JSON-RPC 通信。设备层操作使用 adb;运行时断点、调用栈和变量读取使用 JDI,并通过 adb 转发出来的 JDWP 端口连接 app。
attach 流程:
- 根据 package 或 process name 找到目标进程 pid。
- 把本地 TCP 端口转发到 app 的 JDWP endpoint。
- 使用 JDI
SocketAttachattach。 - 使用返回的
VirtualMachine处理断点、事件队列、stack frame 和变量。
断点行为:
- 如果 class 已加载,server 立即安装
BreakpointRequest。 - 如果 class 尚未加载,server 先安装
ClassPrepareRequest,等 class 加载后再创建真正断点。 - 断点使用
SUSPEND_EVENT_THREAD,因此只暂停命中的线程。
环境要求
- 带
jdk.jdi模块的 JDK。 - Android SDK platform-tools。
adb在 PATH 中可用,或者设置ANDROID_DEBUG_MCP_ADB。- 已连接并开启 USB debugging 的 Android 设备或模拟器。
- 目标 app 必须是 debuggable。
构建
./gradlew build
./gradlew installDist
运行 MCP server:
build/install/android-debug-mcp/bin/android-debug-mcp
server 会在 stdio 上等待 MCP JSON-RPC 消息。
AI 使用指南
推荐工作流是:你描述 Android 运行时问题,AI 选择断点和变量路径,MCP server 负责真实的设备操作、JDWP/JDI attach、断点处理、变量读取和证据记录。
1. 准备目标 App
确保 Android app 是 debuggable,并且正在已连接的设备或模拟器上运行:
adb devices
AI 至少需要这些信息:
packageName:目标 app 的 Android 包名。projectRoot:本地源码目录,用于查找 class 和行号。- 一个具体的 bug、操作流程或运行时问题。
可选但有用的信息:
processName:当目标是子进程时需要提供。deviceSerial:当连接多个设备时需要提供。- 可疑 class、method、路由、日志或 UI 操作步骤。
2. 安装 Codex 插件
本仓库在 codex-marketplace/ 下提供了一个可选的本地 Codex 插件。
codex plugin marketplace add /path/to/android-debug-mcp/codex-marketplace
codex plugin add android-debug-mcp@android-debug-local
让插件知道当前仓库路径:
export ANDROID_DEBUG_MCP_REPO=/path/to/android-debug-mcp
如果你的 Codex 环境没有继承 shell 环境变量,可以直接编辑 codex-marketplace/plugins/android-debug-mcp/.mcp.json,把 ${ANDROID_DEBUG_MCP_REPO...} 替换为仓库绝对路径。
3. 新开 AI 会话
安装插件后,新开一个 Codex 会话,让 MCP server 和 skill 被加载。
提示词模板:
用 $android-runtime-debug 调试我的 Android App。
packageName: com.example.app
processName: com.example.app
projectRoot: /path/to/android-project
问题:
我在首页点击 Refresh 后,列表没有更新。
请先查看当前屏幕和 UI 树,再从源码里选择精确的 Java/Kotlin 断点。命中断点后读取关键变量,每次暂停后都要 resume,最后 detach。
请保留本次 session 的 breakpoint evidence。最后按 File.kt:Lxx 汇总观察到的变量值,并使用 breakpointOrder 解释真实运行时顺序。
多设备场景:
deviceSerial: emulator-5554
子进程场景:
processName: com.example.app:worker
4. AI 通常会调用哪些工具
device_list
screen_snapshot
source_resolve
debug_attach
debug_set_breakpoint
action_execute
debug_wait_event
debug_read_variables
debug_resume
debug_breakpoint_results
timeline_export
debug_detach
如果 Kotlin 行号映射不稳定,AI 可能会使用:
debug_set_breakpoint_group
对于确定性的重复采集,AI 可能会使用短 plan:
debug_run_plan
debug_run_plan 应该保持短小。它适合减少重复的 wait/read/resume 循环,但不应该替代 AI 的判断。如果运行时证据和预期不一致,AI 应该暂停、查看证据,并交互式决定下一步。
5. 好的提问方式
用 $android-runtime-debug。
App 包名是 com.example.app,源码在 /path/to/android-project。
在 Search 页面输入关键词并按 Enter 后,结果列表为空。
请找出 query request 在哪里构造,读取运行时 query 参数,并导出 timeline HTML 报告。
如果你需要基于证据分析 bug:
用 $android-runtime-debug。
保留 breakpoint evidence。
每次断点命中后,读取足够证明状态错误原因的变量。
最后使用 breakpointOrder 和每个 File.kt:Lxx 下的变量值解释根因。
6. AI 必须遵守的规则
- 每次断点命中后都必须调用
debug_resume或debug_detach。 - 优先读取小而明确的变量路径,不要直接展开巨大对象。
- Kotlin coroutine、lambda、inline frame 场景下使用
autoResolveFrame=true。 - 需要长期保存报告时使用
timeline_export(includeHtml=true)。 - 需要断点证据时,最终分析前调用
debug_breakpoint_results。 - 不要要求 server 修改变量或任意调用 app 方法。
- 不要使用任意
adb shell,请使用action_execute。
7. 不通过 Codex 直接使用 MCP
任意 MCP client 都可以启动:
build/install/android-debug-mcp/bin/android-debug-mcp
通信方式是 stdio。client 应先调用 initialize,再调用 tools/list,最后用 tools/call 调具体工具。
典型调试流程
列出设备:
{"name": "device_list", "arguments": {}}
获取当前屏幕状态:
{
"name": "screen_snapshot",
"arguments": {
"includeScreenshot": true,
"includeUiTree": true
}
}
解析源码位置:
{
"name": "source_resolve",
"arguments": {
"className": "com.example.app.feature.HomeFragment",
"projectRoot": "/path/to/android-project",
"maxResults": 20
}
}
Attach 到 app:
{
"name": "debug_attach",
"arguments": {
"packageName": "com.example.app",
"processName": "com.example.app"
}
}
设置断点:
{
"name": "debug_set_breakpoint",
"arguments": {
"sessionId": "session-...",
"className": "com.example.app.feature.HomeFragment",
"line": 123
}
}
触发目标代码路径:
{
"name": "action_execute",
"arguments": {
"sessionId": "session-...",
"action": {
"type": "tap_text",
"text": "Refresh",
"match": "exact"
}
}
}
等待断点命中:
{
"name": "debug_wait_event",
"arguments": {
"sessionId": "session-...",
"timeoutMs": 15000,
"includeLocals": false,
"includeScreen": true
}
}
读取变量:
{
"name": "debug_read_variables",
"arguments": {
"sessionId": "session-...",
"frameId": 0,
"paths": ["this", "request", "this.state"],
"autoResolveFrame": true,
"frameSearchLimit": 20,
"maxDepth": 1,
"maxFields": 20,
"maxArrayItems": 20,
"maxStringLength": 1000
}
}
恢复执行:
{"name": "debug_resume", "arguments": {"sessionId": "session-..."}}
每次断点命中后,都必须 resume 或 detach。
读取断点证据:
{
"name": "debug_breakpoint_results",
"arguments": {
"sessionId": "session-...",
"maxHits": 200
}
}
导出 timeline:
{
"name": "timeline_export",
"arguments": {
"sessionId": "session-...",
"includeHtml": true
}
}
Detach:
{"name": "debug_detach", "arguments": {"sessionId": "session-..."}}
设备操作
常用 allowlisted actions:
{"type": "tap", "x": 300, "y": 1200}
{"type": "swipe", "x1": 610, "y1": 2100, "x2": 610, "y2": 700, "durationMs": 500}
{"type": "text", "text": "hello"}
{"type": "keyevent", "keyCode": 66}
{"type": "back"}
{"type": "home"}
{"type": "enter"}
{"type": "launch", "packageName": "com.example.app"}
{"type": "pull_to_refresh"}
{"type": "tap_text", "text": "Refresh", "match": "exact"}
{"type": "tap_content_desc", "contentDescription": "Back"}
{"type": "scroll_to_text", "text": "Load more", "direction": "down", "maxSwipes": 8}
{"type": "tap_bounds", "bounds": {"left": 10, "top": 20, "right": 180, "bottom": 88}}
候选断点组
当 Kotlin 行号映射不稳定时,可以一次安装多个候选行,让 server 只在目标变量可读时停下来。
{
"name": "debug_set_breakpoint_group",
"arguments": {
"sessionId": "session-...",
"name": "request-state",
"candidates": [
{"className": "com.example.app.feature.HomeFragment", "line": 120},
{"className": "com.example.app.feature.HomeFragment", "line": 128},
{"className": "com.example.app.feature.HomeFragment", "line": 136}
],
"requiredPaths": ["request", "this.state"],
"autoResumeUntilReadable": true,
"frameSearchLimit": 20
}
}
短 Debug Plan 示例
{
"name": "debug_run_plan",
"arguments": {
"sessionId": "session-...",
"plan": {
"name": "one-off-refresh-trace",
"setup": [
{
"kind": "line_breakpoint",
"className": "com.example.app.feature.HomeFragment",
"line": 123
}
],
"trigger": [
{
"kind": "android_action",
"action": {
"type": "tap_text",
"text": "Refresh",
"match": "exact"
}
}
],
"onEvent": [
{
"match": {"event": "breakpoint_hit"},
"actions": [
{"kind": "snapshot", "depth": 12},
{
"kind": "read_variables",
"paths": ["request", "this.state"],
"autoResolveFrame": true,
"frameSearchLimit": 20
},
{"kind": "resume"}
]
}
],
"limits": {
"timeoutMs": 60000,
"maxEvents": 20
}
}
}
}
工具列表
| Tool | 用途 |
| --- | --- |
| device_list | 列出 adb 设备 |
| screen_snapshot | 获取截图、UI 树、屏幕尺寸和当前 Activity |
| action_execute | 执行受控 Android 输入动作 |
| source_resolve | 将 JVM/Kotlin class name 解析为源码候选 |
| debug_attach | Attach 到 debuggable Android 进程 |
| debug_detach | Detach 并移除 adb forward |
| debug_set_breakpoint | 设置 class + line 断点 |
| debug_clear_breakpoint | 删除断点 |
| debug_list_breakpoints | 查看断点状态 |
| debug_wait_event | 等待断点命中 |
| debug_read_variables | 从暂停 frame 读取变量 |
| debug_set_breakpoint_group | 设置候选断点行 |
| debug_clear_breakpoint_group | 清理候选断点组 |
| debug_list_breakpoint_groups | 查看候选断点组 |
| debug_resume | 恢复暂停事件 |
| debug_frame_snapshot | 缓存当前调用栈 |
| debug_get_snapshot | 读取缓存的 frame snapshot |
| debug_breakpoint_results | 按源码行读取断点证据 |
| debug_clear_breakpoint_results | 清理断点证据 |
| debug_run_plan | 运行短 server-side debug plan |
| debug_plan_events | 读取或等待 plan 进度事件 |
| debug_get_plan_report | 读取当前或最终 plan 报告 |
| debug_abort_plan | 中止运行中的 plan |
| debug_pause_plan | 请求 plan 在下次事件处暂停 |
| timeline_get | 读取 session timeline |
| timeline_export | 导出 session.json 和可选 session.html |
| timeline_clear | 清理 timeline |
开发检查
./gradlew -q build
./gradlew -q installDist
列出 MCP tools:
printf 'Content-Length: 58\r\n\r\n{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}Content-Length: 58\r\n\r\n{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
| build/install/android-debug-mcp/bin/android-debug-mcp
常见问题
pid not found:app 进程没有运行,或processName不正确。no_location:该行不可执行、本地源码和 APK 不匹配,或 Kotlin 行号映射发生偏移。no suspended breakpoint event:需要先调用debug_wait_event,或让 debug plan pause/yield。vm_in_plan:当前有 plan 正在运行;应先读取 plan event、暂停 plan 或中止 plan,再执行会改变 session 状态的操作。- 变量输出过大:降低渲染限制,或使用 projection/artifact 选项。
设计原则
- AI 决定下一步调试策略。
- server 执行可靠的底层运行时操作。
- 运行时结论应该来自断点、变量、屏幕和 timeline 证据。
- Debug plan 是短距离加速器,不是提前写死的推理脚本。
- 默认行为保持 read-only,除非是明确的 UI input action 和 resume/detach。