mobile-debug-mcp 0.32.4 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/plugins/marketplace.json +20 -0
- package/.claude-plugin/marketplace.json +14 -0
- package/.cursor-plugin/marketplace.json +16 -0
- package/.github/copilot-instructions.md +7 -0
- package/.github/workflows/ci.yml +21 -0
- package/AGENTS.md +21 -10
- package/README.md +69 -4
- package/agent-plugin/.claude-plugin/plugin.json +20 -0
- package/agent-plugin/.codex-plugin/plugin.json +34 -0
- package/agent-plugin/.cursor-plugin/plugin.json +23 -0
- package/agent-plugin/.mcp.json +14 -0
- package/agent-plugin/README.md +9 -3
- package/agent-plugin/assets/mobile-debug-tools.svg +28 -0
- package/agent-plugin/mcp.json +1 -1
- package/agent-plugin/plugin.json +1 -1
- package/agent-plugin/skills/mobile-debug-mcp/SKILL.md +113 -1
- package/dist/debugger/android.js +78 -0
- package/dist/debugger/guard.js +30 -0
- package/dist/debugger/sessions.js +262 -0
- package/dist/debugger/worker.js +165 -0
- package/dist/interact/android.js +10 -4
- package/dist/interact/conditional-action.js +68 -0
- package/dist/interact/index.js +70 -14
- package/dist/interact/ios.js +14 -8
- package/dist/manage/android.js +4 -3
- package/dist/manage/ios.js +3 -2
- package/dist/observe/android.js +25 -4
- package/dist/observe/evidence.js +280 -0
- package/dist/observe/index.js +33 -6
- package/dist/observe/ios.js +25 -6
- package/dist/observe/readiness-service.js +124 -0
- package/dist/observe/readiness.js +88 -0
- package/dist/observe/recording.js +165 -0
- package/dist/observe/snapshot-metadata.js +10 -2
- package/dist/observe/system-trace-android.js +135 -0
- package/dist/observe/system-trace.js +285 -0
- package/dist/server/common.js +9 -0
- package/dist/server/condition-schema.js +32 -0
- package/dist/server/debugger-handlers.js +38 -0
- package/dist/server/debugger-schema.js +42 -0
- package/dist/server/journey-schema.js +4 -2
- package/dist/server/readiness-schema.js +12 -0
- package/dist/server/system-trace-schema.js +17 -0
- package/dist/server/tool-definitions.js +51 -0
- package/dist/server/tool-handlers.js +294 -105
- package/dist/server-core.js +3 -3
- package/dist/server.js +34 -0
- package/dist/system/index.js +5 -4
- package/dist/utils/exec.js +2 -0
- package/dist/utils/ios/utils.js +4 -4
- package/dist/utils/java.js +14 -12
- package/dist/utils/operation-budget.js +26 -2
- package/dist/utils/resolve-device.js +9 -0
- package/docs/CHANGELOG.md +15 -0
- package/docs/specs/020-conditional-actions/intent.md +62 -0
- package/docs/specs/020-conditional-actions/plan.md +214 -0
- package/docs/specs/020-conditional-actions/spec.md +301 -0
- package/docs/specs/021-app-readiness-signals/intent.md +40 -0
- package/docs/specs/021-app-readiness-signals/plan.md +102 -0
- package/docs/specs/021-app-readiness-signals/spec.md +144 -0
- package/docs/specs/022-correlated-evidence/device-evidence.json +60 -0
- package/docs/specs/022-correlated-evidence/intent.md +37 -0
- package/docs/specs/022-correlated-evidence/modul8-evidence.json +1062 -0
- package/docs/specs/022-correlated-evidence/modul8-selector-fix-evidence.json +336 -0
- package/docs/specs/022-correlated-evidence/plan.md +171 -0
- package/docs/specs/022-correlated-evidence/spec.md +123 -0
- package/docs/specs/023-android-system-traces/device-evidence.json +38 -0
- package/docs/specs/023-android-system-traces/emulator-recheck.json +38 -0
- package/docs/specs/023-android-system-traces/intent.md +40 -0
- package/docs/specs/023-android-system-traces/plan.md +117 -0
- package/docs/specs/023-android-system-traces/spec.md +120 -0
- package/docs/specs/024-android-heap-dump-capture/intent.md +39 -0
- package/docs/specs/025-android-runtime-debugger/intent.md +40 -0
- package/docs/specs/025-android-runtime-debugger/plan.md +104 -0
- package/docs/specs/025-android-runtime-debugger/spec.md +182 -0
- package/docs/specs/process.md +213 -0
- package/docs/tools/TOOLS.md +5 -0
- package/docs/tools/debugger.md +104 -0
- package/docs/tools/evidence.md +82 -0
- package/docs/tools/interact.md +125 -2
- package/docs/tools/observe.md +70 -0
- package/docs/tools/system-traces.md +126 -0
- package/package.json +1 -1
- package/scripts/agent-plugin.mjs +68 -8
- package/src/debugger/android.ts +65 -0
- package/src/debugger/guard.ts +26 -0
- package/src/debugger/sessions.ts +168 -0
- package/src/debugger/worker.ts +122 -0
- package/src/interact/android.ts +10 -4
- package/src/interact/conditional-action.ts +75 -0
- package/src/interact/index.ts +57 -28
- package/src/interact/ios.ts +14 -8
- package/src/manage/android.ts +4 -3
- package/src/manage/ios.ts +3 -2
- package/src/observe/android.ts +33 -6
- package/src/observe/evidence.ts +262 -0
- package/src/observe/index.ts +32 -8
- package/src/observe/ios.ts +32 -8
- package/src/observe/readiness-service.ts +94 -0
- package/src/observe/readiness.ts +84 -0
- package/src/observe/snapshot-metadata.ts +12 -1
- package/src/observe/system-trace-android.ts +133 -0
- package/src/observe/system-trace.ts +218 -0
- package/src/server/common.ts +7 -0
- package/src/server/condition-schema.ts +32 -0
- package/src/server/debugger-handlers.ts +29 -0
- package/src/server/debugger-schema.ts +37 -0
- package/src/server/journey-schema.ts +4 -2
- package/src/server/readiness-schema.ts +12 -0
- package/src/server/system-trace-schema.ts +17 -0
- package/src/server/tool-definitions.ts +49 -0
- package/src/server/tool-handlers.ts +182 -21
- package/src/server-core.ts +3 -3
- package/src/server.ts +24 -0
- package/src/system/index.ts +7 -5
- package/src/types.ts +30 -1
- package/src/utils/exec.ts +2 -0
- package/src/utils/ios/utils.ts +4 -4
- package/src/utils/java.ts +13 -12
- package/src/utils/operation-budget.ts +24 -2
- package/src/utils/resolve-device.ts +10 -0
- package/test/device/automated/debugger/runtime.android.test.ts +203 -0
- package/test/device/automated/observe/correlated-evidence.smoke.ts +70 -0
- package/test/device/automated/observe/system-trace.smoke.ts +104 -0
- package/test/device/manual/interact/conditional_actions.manual.ts +95 -0
- package/test/device/manual/interact/modul8_selector_tap.manual.ts +63 -0
- package/test/device/manual/interact/readiness.manual.ts +99 -0
- package/test/fixtures/debugger-app/MainActivity.java +46 -0
- package/test/fixtures/debugger-app/NoLocals.java +7 -0
- package/test/fixtures/debugger-app/README.md +13 -0
- package/test/fixtures/debugger-app/build.ts +29 -0
- package/test/fixtures/readiness-app/MainActivity.java +23 -0
- package/test/fixtures/readiness-app/README.md +12 -0
- package/test/fixtures/readiness-app/build.ts +37 -0
- package/test/fixtures/readiness-app/main.m +27 -0
- package/test/helpers/perfetto.ts +59 -0
- package/test/unit/debugger/guards.test.ts +46 -0
- package/test/unit/debugger/native.test.ts +33 -0
- package/test/unit/debugger/sessions.test.ts +83 -0
- package/test/unit/observe/android_tree_acquisition.test.ts +2 -0
- package/test/unit/observe/capture_debug_snapshot.test.ts +42 -32
- package/test/unit/observe/compact_tree_error.test.ts +24 -0
- package/test/unit/observe/conditional_snapshots.test.ts +33 -0
- package/test/unit/observe/correlated-evidence.test.ts +128 -0
- package/test/unit/observe/ios_tree_acquisition.test.ts +2 -0
- package/test/unit/observe/readiness.test.ts +41 -0
- package/test/unit/observe/readiness_native.test.ts +68 -0
- package/test/unit/observe/system-trace-android.test.ts +52 -0
- package/test/unit/observe/system-trace.test.ts +166 -0
- package/test/unit/server/conditional_actions.test.ts +299 -0
- package/test/unit/server/conditional_schema.test.ts +59 -0
- package/test/unit/server/correlated-evidence.test.ts +194 -0
- package/test/unit/server/readiness.test.ts +170 -0
- package/test/unit/server/selector_tap_target.test.ts +79 -0
- package/test/unit/server/system-trace.test.ts +64 -0
- package/test/unit/server/tap_selector.test.ts +2 -0
- package/worker/DebuggerWorker.java +160 -0
- package/skills/mobile-debug-tools-usage/SKILL.md +0 -96
- /package/{skills → .agents/skills}/README.md +0 -0
- /package/{skills → .agents/skills}/mcp-builder/SKILL.md +0 -0
- /package/{skills → .agents/skills}/mcp-builder/references/build-flags.md +0 -0
- /package/{skills → .agents/skills}/mcp-builder/references/diagnostics-schema.md +0 -0
- /package/{skills → .agents/skills}/mcp-builder/references/toolchain-details.md +0 -0
- /package/{skills → .agents/skills}/spec-review/SKILL.md +0 -0
- /package/{skills → .agents/skills}/spec-review/references/spec-review-checklist.md +0 -0
- /package/{skills → .agents/skills}/spec-review/references/spec-review-template.md +0 -0
- /package/{skills → .agents/skills}/test-authoring/SKILL.md +0 -0
- /package/{skills → .agents/skills}/test-authoring/references/repo-test-layout.md +0 -0
- /package/{skills → .agents/skills}/test-authoring/references/test-authoring-workflow.md +0 -0
- /package/{skills → .agents/skills}/test-authoring/references/test-quality-checklist.md +0 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import com.sun.jdi.*;
|
|
2
|
+
import com.sun.jdi.connect.*;
|
|
3
|
+
import com.sun.jdi.event.*;
|
|
4
|
+
import com.sun.jdi.request.*;
|
|
5
|
+
import java.io.*;
|
|
6
|
+
import java.nio.charset.StandardCharsets;
|
|
7
|
+
import java.util.*;
|
|
8
|
+
import java.util.concurrent.*;
|
|
9
|
+
|
|
10
|
+
/** Standalone JDI bridge. No debuggee method invocation, evaluation or mutation. */
|
|
11
|
+
public final class DebuggerWorker {
|
|
12
|
+
static final int MAX_LINE = 1048576;
|
|
13
|
+
final Object lock = new Object();
|
|
14
|
+
VirtualMachine vm;
|
|
15
|
+
final Map<String, Map<String,Object>> breaks = new LinkedHashMap<>();
|
|
16
|
+
final Map<String, StackFrame> frames = new HashMap<>();
|
|
17
|
+
final Map<String, ThreadReference> threads = new HashMap<>();
|
|
18
|
+
final List<EventSet> held = new ArrayList<>();
|
|
19
|
+
String stopId;
|
|
20
|
+
long attachedAt, pausedAt, activityAt;
|
|
21
|
+
boolean closed;
|
|
22
|
+
StepRequest step;
|
|
23
|
+
final ScheduledExecutorService watchdog = Executors.newSingleThreadScheduledExecutor(r -> { Thread t=new Thread(r,"debugger-watchdog"); t.setDaemon(true); return t; });
|
|
24
|
+
static Map<String,Object> obj(Object... pairs) { Map<String,Object> m=new LinkedHashMap<>(); for(int i=0;i<pairs.length;i+=2)m.put((String)pairs[i],pairs[i+1]);return m; }
|
|
25
|
+
static synchronized void send(Object o) { System.out.println(Json.write(o)); System.out.flush(); }
|
|
26
|
+
static String str(Map<String,Object> m,String k) { Object v=m.get(k); return v==null?"":v.toString(); }
|
|
27
|
+
static int num(Map<String,Object> m,String k,int d) { Object v=m.get(k);return v instanceof Number?((Number)v).intValue():d; }
|
|
28
|
+
static RuntimeException fail(String code) { return new IllegalStateException(code); }
|
|
29
|
+
void active() { if(vm==null || closed)throw fail("DEBUGGER_DISCONNECTED"); }
|
|
30
|
+
void paused(Map<String,Object> a) { active();if(stopId==null)throw fail("DEBUGGER_NOT_PAUSED");if(!stopId.equals(str(a,"stopId")))throw fail("DEBUGGER_STALE_REFERENCE"); }
|
|
31
|
+
void invalidate() { stopId=null;frames.clear();threads.clear();pausedAt=0; }
|
|
32
|
+
void cancelStep() { if(step!=null){try{vm.eventRequestManager().deleteEventRequest(step);}catch(RuntimeException ignored){}step=null;} }
|
|
33
|
+
void release() { for(EventSet s:held)s.resume();held.clear();invalidate(); }
|
|
34
|
+
Map<String,Object> location(Location l) { String source=null;try{source=l.sourceName();}catch(AbsentInformationException ignored){}return obj("class_name",l.declaringType().name(),"method",l.method().name(),"source",source,"line",l.lineNumber(),"code_index",l.codeIndex(),"class_loader_id",l.declaringType().classLoader()==null?"bootstrap":String.valueOf(l.declaringType().classLoader().uniqueID())); }
|
|
35
|
+
String thread(ThreadReference t) { String id=stopId+":"+t.uniqueID();threads.put(id,t);return id; }
|
|
36
|
+
Map<String,Object> state() {return obj("state",closed?"detached":stopId==null?"running":"paused","stop_id",stopId,"breakpoints",new ArrayList<>(breaks.values()));}
|
|
37
|
+
void bind(Map<String,Object> b,ReferenceType type) {
|
|
38
|
+
try {
|
|
39
|
+
List<Location> ls=type.locationsOfLine(((Number)b.get("line")).intValue());
|
|
40
|
+
if(ls.isEmpty()){ if(!"verified".equals(b.get("status")))b.put("status","rejected"); b.put("reason","DEBUGGER_LOCATION_UNAVAILABLE");return; }
|
|
41
|
+
@SuppressWarnings("unchecked") List<Object> locations=(List<Object>)b.get("locations");
|
|
42
|
+
for(Location l:ls){
|
|
43
|
+
Map<String,Object> loc=location(l); if(locations.contains(loc))continue;
|
|
44
|
+
BreakpointRequest req=vm.eventRequestManager().createBreakpointRequest(l);req.putProperty("id",b.get("breakpoint_id"));req.setSuspendPolicy(EventRequest.SUSPEND_ALL);req.enable();locations.add(loc);
|
|
45
|
+
}
|
|
46
|
+
b.put("status","verified");b.remove("reason");
|
|
47
|
+
} catch(AbsentInformationException e){if(!"verified".equals(b.get("status")))b.put("status","rejected");b.put("reason","DEBUGGER_LOCATION_UNAVAILABLE");}
|
|
48
|
+
}
|
|
49
|
+
void eventLoop() {
|
|
50
|
+
try {
|
|
51
|
+
while(!closed){
|
|
52
|
+
EventSet set=vm.eventQueue().remove(200);
|
|
53
|
+
if(set==null)continue;
|
|
54
|
+
synchronized(lock){
|
|
55
|
+
if(closed)break;
|
|
56
|
+
List<Object> stops=new ArrayList<>();ThreadReference trigger=null;Set<ReferenceType> prepared=new HashSet<>();
|
|
57
|
+
for(Event event:set){
|
|
58
|
+
if(event instanceof VMDeathEvent || event instanceof VMDisconnectEvent){finish("process_exit");return;}
|
|
59
|
+
if(event instanceof ClassPrepareEvent c && prepared.add(c.referenceType())){ for(Map<String,Object>b:breaks.values())if(!"removed".equals(b.get("status")) && b.get("className").equals(c.referenceType().name())){bind(b,c.referenceType());send(obj("event","breakpoint","breakpoint",b));} }
|
|
60
|
+
if(event instanceof BreakpointEvent b){trigger=b.thread();stops.add(obj("reason","breakpoint","location",location(b.location()),"breakpoint_id",b.request().getProperty("id")));}
|
|
61
|
+
if(event instanceof StepEvent s){trigger=s.thread();stops.add(obj("reason","step","location",location(s.location())));}
|
|
62
|
+
}
|
|
63
|
+
if(!stops.isEmpty()){
|
|
64
|
+
cancelStep();held.add(set);
|
|
65
|
+
// Additional simultaneously suspended sets belong to the same epoch.
|
|
66
|
+
if(stopId==null){stopId=UUID.randomUUID().toString();pausedAt=System.nanoTime();}
|
|
67
|
+
send(obj("event","stopped","state","paused","stop_id",stopId,"thread_id",thread(trigger),"stops",stops));
|
|
68
|
+
}else set.resume();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}catch(InterruptedException e){Thread.currentThread().interrupt();}catch(RuntimeException e){synchronized(lock){finish("transport_lost");}}
|
|
72
|
+
}
|
|
73
|
+
void finish(String reason) {
|
|
74
|
+
if(closed)return;
|
|
75
|
+
boolean clean=true;
|
|
76
|
+
try{if(vm!=null){cancelStep();vm.dispose();}}catch(VMDisconnectedException ignored){}catch(RuntimeException e){clean=false;}
|
|
77
|
+
closed=true;held.clear();invalidate();
|
|
78
|
+
send(obj("event","terminated","reason",reason,"cleanup_complete",clean,"execution_state","unknown"));
|
|
79
|
+
}
|
|
80
|
+
Object command(Map<String,Object> a) throws Exception {
|
|
81
|
+
String cmd=str(a,"command");activityAt=System.nanoTime();
|
|
82
|
+
if(cmd.equals("attach")){
|
|
83
|
+
if(vm!=null)throw fail("DEBUGGER_BUSY");
|
|
84
|
+
AttachingConnector c=Bootstrap.virtualMachineManager().attachingConnectors().stream().filter(x->x.name().equals("com.sun.jdi.SocketAttach")).findFirst().orElseThrow();
|
|
85
|
+
Map<String,Connector.Argument> args=c.defaultArguments();args.get("hostname").setValue("127.0.0.1");args.get("port").setValue(str(a,"port"));args.get("timeout").setValue(str(a,"timeoutMs"));
|
|
86
|
+
vm=c.attach(args);attachedAt=System.nanoTime();
|
|
87
|
+
Thread t=new Thread(this::eventLoop,"debugger-events");t.setDaemon(true);t.start();
|
|
88
|
+
return state();
|
|
89
|
+
}
|
|
90
|
+
if(cmd.equals("detach")){finish("requested");return state();}
|
|
91
|
+
active();
|
|
92
|
+
if(cmd.equals("heartbeat"))return obj("alive",true);
|
|
93
|
+
if(cmd.equals("state"))return state();
|
|
94
|
+
if(cmd.equals("set")){
|
|
95
|
+
if(breaks.size()>=100)throw fail("DEBUGGER_BUSY");
|
|
96
|
+
String id=UUID.randomUUID().toString();Map<String,Object>b=obj("breakpoint_id",id,"className",str(a,"className"),"line",num(a,"line",0),"status","pending","locations",new ArrayList<>());
|
|
97
|
+
breaks.put(id,b);
|
|
98
|
+
ClassPrepareRequest req=vm.eventRequestManager().createClassPrepareRequest();req.addClassFilter(str(a,"className"));req.putProperty("id",id);req.setSuspendPolicy(EventRequest.SUSPEND_ALL);req.enable();
|
|
99
|
+
for(ReferenceType type:vm.classesByName(str(a,"className")))bind(b,type);
|
|
100
|
+
return b;
|
|
101
|
+
}
|
|
102
|
+
if(cmd.equals("remove")){
|
|
103
|
+
String id=str(a,"breakpointId");
|
|
104
|
+
if(!breaks.containsKey(id))throw fail("DEBUGGER_NOT_FOUND");
|
|
105
|
+
for(EventRequest r:new ArrayList<EventRequest>(vm.eventRequestManager().breakpointRequests()))if(id.equals(r.getProperty("id")))vm.eventRequestManager().deleteEventRequest(r);
|
|
106
|
+
for(EventRequest r:new ArrayList<EventRequest>(vm.eventRequestManager().classPrepareRequests()))if(id.equals(r.getProperty("id")))vm.eventRequestManager().deleteEventRequest(r);
|
|
107
|
+
breaks.get(id).put("status","removed");return obj("removed",true);
|
|
108
|
+
}
|
|
109
|
+
paused(a);
|
|
110
|
+
if(cmd.equals("stack")){
|
|
111
|
+
ThreadReference t=threads.get(str(a,"threadId"));if(t==null)throw fail("DEBUGGER_STALE_REFERENCE");
|
|
112
|
+
int count=Math.min(t.frameCount(),1000),offset=num(a,"offset",0),end=Math.min(count,offset+num(a,"limit",20));List<Object> out=new ArrayList<>();
|
|
113
|
+
for(int i=offset;i<end;i++){StackFrame f=t.frame(i);String id=stopId+":"+t.uniqueID()+":"+i;if(!frames.containsKey(id)&&frames.size()>=1000)throw fail("DEBUGGER_BUSY");frames.put(id,f);out.add(obj("frame_id",id,"index",i,"location",location(f.location())));}
|
|
114
|
+
return obj("frames",out,"next_offset",end<count?end:null,"truncated",t.frameCount()>1000);
|
|
115
|
+
}
|
|
116
|
+
if(cmd.equals("locals")){
|
|
117
|
+
StackFrame f=frames.get(str(a,"frameId"));if(f==null)throw fail("DEBUGGER_STALE_REFERENCE");List<Object> out=new ArrayList<>();
|
|
118
|
+
try{List<LocalVariable> vars=f.visibleVariables();if(vars.isEmpty())return obj("availability","unknown","reason","EMPTY_OR_UNAVAILABLE_VARIABLE_METADATA","locals",out,"truncated",false);for(LocalVariable v:vars.subList(0,Math.min(vars.size(),100))){Value value=f.getValue(v);Object display=null;boolean truncated=false;
|
|
119
|
+
if(value instanceof StringReference s){String text=s.value();truncated=text.length()>256;display=text.substring(0,Math.min(text.length(),256));}
|
|
120
|
+
else if(value instanceof PrimitiveValue)display=value.toString();
|
|
121
|
+
else if(value instanceof ObjectReference o)display=obj("object_id",String.valueOf(o.uniqueID()),"type",o.referenceType().name());
|
|
122
|
+
out.add(obj("name",v.name(),"type",v.typeName(),"value",display,"truncated",truncated));}
|
|
123
|
+
return obj("availability","available","locals",out,"truncated",vars.size()>100);
|
|
124
|
+
}catch(AbsentInformationException e){return obj("availability","debug_metadata_unavailable","locals",out,"truncated",false);}
|
|
125
|
+
}
|
|
126
|
+
if(cmd.equals("resume") || cmd.equals("step")){
|
|
127
|
+
if(cmd.equals("step")){
|
|
128
|
+
ThreadReference t=threads.get(str(a,"threadId"));if(t==null)throw fail("DEBUGGER_STALE_REFERENCE");cancelStep();
|
|
129
|
+
int depth=switch(str(a,"kind")){case "into"->StepRequest.STEP_INTO;case "over"->StepRequest.STEP_OVER;case "out"->StepRequest.STEP_OUT;default->throw fail("INVALID_ARGUMENT");};
|
|
130
|
+
step=vm.eventRequestManager().createStepRequest(t,StepRequest.STEP_LINE,depth);step.addCountFilter(1);step.setSuspendPolicy(EventRequest.SUSPEND_ALL);step.enable();
|
|
131
|
+
}else cancelStep();
|
|
132
|
+
release();return state();
|
|
133
|
+
}
|
|
134
|
+
throw fail("INVALID_ARGUMENT");
|
|
135
|
+
}
|
|
136
|
+
void run() throws IOException {
|
|
137
|
+
watchdog.scheduleAtFixedRate(()->{synchronized(lock){if(vm==null||closed)return;long now=System.nanoTime();String reason=pausedAt!=0&&now-pausedAt>=TimeUnit.SECONDS.toNanos(60)?"pause_limit":now-activityAt>=TimeUnit.MINUTES.toNanos(5)?"idle_limit":now-attachedAt>=TimeUnit.MINUTES.toNanos(30)?"session_limit":null;if(reason!=null)finish(reason);}},1,1,TimeUnit.SECONDS);
|
|
138
|
+
Runtime.getRuntime().addShutdownHook(new Thread(()->{synchronized(lock){finish("worker_shutdown");}}));
|
|
139
|
+
try(Reader reader=new InputStreamReader(System.in,StandardCharsets.UTF_8)){
|
|
140
|
+
StringBuilder line=new StringBuilder();int ch;
|
|
141
|
+
while((ch=reader.read())!=-1){if(ch!='\n'){line.append((char)ch);if(line.length()>MAX_LINE)throw new IOException("IPC_LIMIT");continue;}
|
|
142
|
+
Object id=null;
|
|
143
|
+
try{@SuppressWarnings("unchecked") Map<String,Object>a=(Map<String,Object>)new Json(line.toString()).value();id=a.get("id");synchronized(lock){Object result=command(a);send(obj("id",id,"result",result));}}
|
|
144
|
+
catch(Exception e){String msg=e.getMessage();String code=msg!=null&&msg.matches("(?:DEBUGGER|INVALID|PROCESS|DEVICE)_[A-Z_]+")?msg:e instanceof VMDisconnectedException?"DEBUGGER_DISCONNECTED":"DEBUGGER_WORKER_FAILED";send(obj("id",id,"error",obj("code",code)));}
|
|
145
|
+
line.setLength(0);
|
|
146
|
+
}
|
|
147
|
+
}finally{synchronized(lock){finish("parent_closed");}watchdog.shutdownNow();}
|
|
148
|
+
}
|
|
149
|
+
public static void main(String[] args) throws Exception {new DebuggerWorker().run();}
|
|
150
|
+
|
|
151
|
+
// Small strict JSON codec for the bounded, whitelisted bridge protocol.
|
|
152
|
+
static final class Json {
|
|
153
|
+
final String s;int p;Json(String s){this.s=s;}
|
|
154
|
+
void ws(){while(p<s.length()&&Character.isWhitespace(s.charAt(p)))p++;}
|
|
155
|
+
Object value(){Object v=read();ws();if(p!=s.length())throw fail("INVALID_ARGUMENT");return v;}
|
|
156
|
+
Object read(){ws();char c=s.charAt(p++);if(c=='"')return string();if(c=='{'){Map<String,Object>m=new LinkedHashMap<>();ws();if(s.charAt(p)=='}'){p++;return m;}do{ws();if(s.charAt(p++)!='"')throw fail("INVALID_ARGUMENT");String k=string();ws();if(s.charAt(p++)!=':')throw fail("INVALID_ARGUMENT");m.put(k,read());ws();c=s.charAt(p++);}while(c==',');if(c!='}')throw fail("INVALID_ARGUMENT");return m;}if(c=='['){List<Object>l=new ArrayList<>();ws();if(s.charAt(p)==']'){p++;return l;}do{l.add(read());ws();c=s.charAt(p++);}while(c==',');if(c!=']')throw fail("INVALID_ARGUMENT");return l;}p--;for(String lit:List.of("true","false","null"))if(s.startsWith(lit,p)){p+=lit.length();return lit.equals("null")?null:Boolean.valueOf(lit);}int start=p;while(p<s.length()&&"-+0123456789.eE".indexOf(s.charAt(p))>=0)p++;String number=s.substring(start,p);if(number.indexOf('.')<0&&number.indexOf('e')<0&&number.indexOf('E')<0)return Long.valueOf(number);return Double.valueOf(number);}
|
|
157
|
+
String string(){StringBuilder b=new StringBuilder();while(p<s.length()){char c=s.charAt(p++);if(c=='"')return b.toString();if(c=='\\'){c=s.charAt(p++);c=switch(c){case 'n'->'\n';case 'r'->'\r';case 't'->'\t';case 'b'->'\b';case 'f'->'\f';case 'u'->{char u=(char)Integer.parseInt(s.substring(p,p+4),16);p+=4;yield u;}case '"','\\','/'->c;default->throw fail("INVALID_ARGUMENT");};}else if(c<32)throw fail("INVALID_ARGUMENT");b.append(c);}throw fail("INVALID_ARGUMENT");}
|
|
158
|
+
static String write(Object o){if(o==null)return "null";if(o instanceof String s){StringBuilder b=new StringBuilder("\"");for(char c:s.toCharArray()){switch(c){case '"'->b.append("\\\"");case '\\'->b.append("\\\\");case '\n'->b.append("\\n");case '\r'->b.append("\\r");case '\t'->b.append("\\t");default->{if(c<32||Character.isSurrogate(c))b.append(String.format("\\u%04x",(int)c));else b.append(c);}}}return b.append('"').toString();}if(o instanceof Map<?,?>m){List<String>l=new ArrayList<>();m.forEach((k,v)->l.add(write(k.toString())+":"+write(v)));return "{"+String.join(",",l)+"}";}if(o instanceof Collection<?>l){List<String>a=new ArrayList<>();l.forEach(v->a.add(write(v)));return "["+String.join(",",a)+"]";}return o.toString();}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: mobile-debug-mcp
|
|
3
|
-
description: Use mobile-debug MCP to inspect, interact with, and verify any Android or iOS app on a device or emulator.
|
|
4
|
-
metadata:
|
|
5
|
-
author: mobile-debug-mcp contributors
|
|
6
|
-
version: "1.1"
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# Mobile-debug MCP usage
|
|
10
|
-
|
|
11
|
-
Use mobile-debug MCP when a task needs evidence from a running Android or iOS app. Identify the device and the state or behaviour to check, then choose the smallest observation or action that can establish it. A successful tool call confirms only what that tool reports; check the resulting app state before claiming an outcome.
|
|
12
|
-
|
|
13
|
-
## Choose the lightest evidence that proves the claim
|
|
14
|
-
|
|
15
|
-
- Prefer a screenshot for routine visual navigation, layout checks, and visible
|
|
16
|
-
outcomes that can be established from the image.
|
|
17
|
-
- Use UI-tree reads and accessibility assertions such as `expect_element_visible`,
|
|
18
|
-
`expect_state`, or `wait_for_ui` when reliable element selection or a structured
|
|
19
|
-
property check is required. An image alone does not establish accessibility
|
|
20
|
-
properties such as enabled state.
|
|
21
|
-
- Use a debug snapshot when an ordinary observation does not explain a failure or multiple kinds of evidence are needed together.
|
|
22
|
-
- Use logs or network activity for claims about errors or requests. Correlate them with the relevant action and time window; they do not establish what the user saw.
|
|
23
|
-
- Use platform shell commands only for device or system operations that the MCP tools cannot expose. Treat shell output as evidence of that operation, not of the app's visible state.
|
|
24
|
-
|
|
25
|
-
Choose one observation that proves the required outcome. Avoid automatically
|
|
26
|
-
reading the UI tree after a screenshot or taking a screenshot after a sufficient
|
|
27
|
-
structured assertion.
|
|
28
|
-
|
|
29
|
-
## Keep Android inspection APK-free
|
|
30
|
-
|
|
31
|
-
Use the standard ADB path for Android inspection. It requires no helper APK.
|
|
32
|
-
The normal hierarchy dump
|
|
33
|
-
can take several seconds; account for this rather than treating it as a fast
|
|
34
|
-
poll. Compressed dumps can omit nodes and should not be assumed faster.
|
|
35
|
-
|
|
36
|
-
## Keep interaction fast
|
|
37
|
-
|
|
38
|
-
Treat each MCP call as a round trip and use the fastest path that still proves
|
|
39
|
-
the behaviour. For an interaction, prefer this sequence:
|
|
40
|
-
|
|
41
|
-
1. Observe the current screen or wait for a uniquely identifiable target if its presence is uncertain.
|
|
42
|
-
2. Perform the requested action using a stable selector or element id when available.
|
|
43
|
-
3. Check the specific resulting state once it has had time to occur.
|
|
44
|
-
|
|
45
|
-
Do not wait before every action when its target and current state are already
|
|
46
|
-
known. Do not poll unchanged screens. Set bounded assertion timeouts that cover
|
|
47
|
-
the expected app transition plus a complete fresh observation and a margin.
|
|
48
|
-
For Android ADB tree reads taking 2–3 seconds, start with a 5-second timeout for
|
|
49
|
-
a simple assertion; allow more when loading or repeated observations are
|
|
50
|
-
required. A 2-second timeout cannot reliably accommodate a 3-second tree read.
|
|
51
|
-
Adjust to measured device latency and supported tool limits. Report an
|
|
52
|
-
observation timeout separately from evidence that a control is absent.
|
|
53
|
-
|
|
54
|
-
Use compact responses for routine actions. If action dispatch is reliable but
|
|
55
|
-
verification is slow or unavailable, dispatch with verification disabled and
|
|
56
|
-
verify at meaningful checkpoints with one focused accessibility assertion or
|
|
57
|
-
screenshot. Do not assume `light` verification is faster: use it only when it
|
|
58
|
-
has returned useful results reliably on the current MCP version and device.
|
|
59
|
-
|
|
60
|
-
Prefer a stable element selector when it is already known and valid. For a
|
|
61
|
-
clearly identifiable visual target, use coordinates from a fresh screenshot
|
|
62
|
-
without requiring a tree read solely to rediscover it. If the target is
|
|
63
|
-
ambiguous or a semantic check is required, resolve it from the hierarchy.
|
|
64
|
-
Reacquire coordinates after scrolling,
|
|
65
|
-
changing orientation, switching screens, or opening a system surface.
|
|
66
|
-
|
|
67
|
-
Element discovery can be the slowest part of a journey. Avoid repeated
|
|
68
|
-
`find_element` calls for the same screen: resolve a control once, use the
|
|
69
|
-
returned selector or coordinates while that screen is unchanged, and verify
|
|
70
|
-
the resulting state before continuing. If a query fails, inspect the current
|
|
71
|
-
screen once before retrying; controls represented only by icons may not have a
|
|
72
|
-
matching visible text label.
|
|
73
|
-
|
|
74
|
-
When the MCP offers a journey or batch action, use it only when its steps can
|
|
75
|
-
express the needed observable checks and it returns enough evidence to report
|
|
76
|
-
the outcome. Measure the whole journey and compare equivalent steps on the same
|
|
77
|
-
device; do not infer a performance improvement from a single fast action.
|
|
78
|
-
|
|
79
|
-
## Match the evidence to the claim
|
|
80
|
-
|
|
81
|
-
For a state change, check the state after the action rather than inferring it
|
|
82
|
-
from successful dispatch. For an asynchronous update, start a fresh observation
|
|
83
|
-
after the relevant event and wait for the expected condition within a bounded
|
|
84
|
-
timeout. If an accessibility tree appears stale or incomplete, use another
|
|
85
|
-
appropriate observation such as a screenshot and report the discrepancy.
|
|
86
|
-
|
|
87
|
-
For behaviour outside the app's foreground screen, inspect the relevant system
|
|
88
|
-
surface or device state. For content that cannot be established visually, such
|
|
89
|
-
as audio output, use a suitable direct check or state that it was not verified.
|
|
90
|
-
|
|
91
|
-
## Report evidence precisely
|
|
92
|
-
|
|
93
|
-
State the device, actions taken, observed result, evidence source, and any
|
|
94
|
-
untested scope. Distinguish an observed outcome from an inference. Do not say
|
|
95
|
-
a feature works solely because the build compiled or an MCP action returned
|
|
96
|
-
success.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|