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.
Files changed (170) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/.claude-plugin/marketplace.json +14 -0
  3. package/.cursor-plugin/marketplace.json +16 -0
  4. package/.github/copilot-instructions.md +7 -0
  5. package/.github/workflows/ci.yml +21 -0
  6. package/AGENTS.md +21 -10
  7. package/README.md +69 -4
  8. package/agent-plugin/.claude-plugin/plugin.json +20 -0
  9. package/agent-plugin/.codex-plugin/plugin.json +34 -0
  10. package/agent-plugin/.cursor-plugin/plugin.json +23 -0
  11. package/agent-plugin/.mcp.json +14 -0
  12. package/agent-plugin/README.md +9 -3
  13. package/agent-plugin/assets/mobile-debug-tools.svg +28 -0
  14. package/agent-plugin/mcp.json +1 -1
  15. package/agent-plugin/plugin.json +1 -1
  16. package/agent-plugin/skills/mobile-debug-mcp/SKILL.md +113 -1
  17. package/dist/debugger/android.js +78 -0
  18. package/dist/debugger/guard.js +30 -0
  19. package/dist/debugger/sessions.js +262 -0
  20. package/dist/debugger/worker.js +165 -0
  21. package/dist/interact/android.js +10 -4
  22. package/dist/interact/conditional-action.js +68 -0
  23. package/dist/interact/index.js +70 -14
  24. package/dist/interact/ios.js +14 -8
  25. package/dist/manage/android.js +4 -3
  26. package/dist/manage/ios.js +3 -2
  27. package/dist/observe/android.js +25 -4
  28. package/dist/observe/evidence.js +280 -0
  29. package/dist/observe/index.js +33 -6
  30. package/dist/observe/ios.js +25 -6
  31. package/dist/observe/readiness-service.js +124 -0
  32. package/dist/observe/readiness.js +88 -0
  33. package/dist/observe/recording.js +165 -0
  34. package/dist/observe/snapshot-metadata.js +10 -2
  35. package/dist/observe/system-trace-android.js +135 -0
  36. package/dist/observe/system-trace.js +285 -0
  37. package/dist/server/common.js +9 -0
  38. package/dist/server/condition-schema.js +32 -0
  39. package/dist/server/debugger-handlers.js +38 -0
  40. package/dist/server/debugger-schema.js +42 -0
  41. package/dist/server/journey-schema.js +4 -2
  42. package/dist/server/readiness-schema.js +12 -0
  43. package/dist/server/system-trace-schema.js +17 -0
  44. package/dist/server/tool-definitions.js +51 -0
  45. package/dist/server/tool-handlers.js +294 -105
  46. package/dist/server-core.js +3 -3
  47. package/dist/server.js +34 -0
  48. package/dist/system/index.js +5 -4
  49. package/dist/utils/exec.js +2 -0
  50. package/dist/utils/ios/utils.js +4 -4
  51. package/dist/utils/java.js +14 -12
  52. package/dist/utils/operation-budget.js +26 -2
  53. package/dist/utils/resolve-device.js +9 -0
  54. package/docs/CHANGELOG.md +15 -0
  55. package/docs/specs/020-conditional-actions/intent.md +62 -0
  56. package/docs/specs/020-conditional-actions/plan.md +214 -0
  57. package/docs/specs/020-conditional-actions/spec.md +301 -0
  58. package/docs/specs/021-app-readiness-signals/intent.md +40 -0
  59. package/docs/specs/021-app-readiness-signals/plan.md +102 -0
  60. package/docs/specs/021-app-readiness-signals/spec.md +144 -0
  61. package/docs/specs/022-correlated-evidence/device-evidence.json +60 -0
  62. package/docs/specs/022-correlated-evidence/intent.md +37 -0
  63. package/docs/specs/022-correlated-evidence/modul8-evidence.json +1062 -0
  64. package/docs/specs/022-correlated-evidence/modul8-selector-fix-evidence.json +336 -0
  65. package/docs/specs/022-correlated-evidence/plan.md +171 -0
  66. package/docs/specs/022-correlated-evidence/spec.md +123 -0
  67. package/docs/specs/023-android-system-traces/device-evidence.json +38 -0
  68. package/docs/specs/023-android-system-traces/emulator-recheck.json +38 -0
  69. package/docs/specs/023-android-system-traces/intent.md +40 -0
  70. package/docs/specs/023-android-system-traces/plan.md +117 -0
  71. package/docs/specs/023-android-system-traces/spec.md +120 -0
  72. package/docs/specs/024-android-heap-dump-capture/intent.md +39 -0
  73. package/docs/specs/025-android-runtime-debugger/intent.md +40 -0
  74. package/docs/specs/025-android-runtime-debugger/plan.md +104 -0
  75. package/docs/specs/025-android-runtime-debugger/spec.md +182 -0
  76. package/docs/specs/process.md +213 -0
  77. package/docs/tools/TOOLS.md +5 -0
  78. package/docs/tools/debugger.md +104 -0
  79. package/docs/tools/evidence.md +82 -0
  80. package/docs/tools/interact.md +125 -2
  81. package/docs/tools/observe.md +70 -0
  82. package/docs/tools/system-traces.md +126 -0
  83. package/package.json +1 -1
  84. package/scripts/agent-plugin.mjs +68 -8
  85. package/src/debugger/android.ts +65 -0
  86. package/src/debugger/guard.ts +26 -0
  87. package/src/debugger/sessions.ts +168 -0
  88. package/src/debugger/worker.ts +122 -0
  89. package/src/interact/android.ts +10 -4
  90. package/src/interact/conditional-action.ts +75 -0
  91. package/src/interact/index.ts +57 -28
  92. package/src/interact/ios.ts +14 -8
  93. package/src/manage/android.ts +4 -3
  94. package/src/manage/ios.ts +3 -2
  95. package/src/observe/android.ts +33 -6
  96. package/src/observe/evidence.ts +262 -0
  97. package/src/observe/index.ts +32 -8
  98. package/src/observe/ios.ts +32 -8
  99. package/src/observe/readiness-service.ts +94 -0
  100. package/src/observe/readiness.ts +84 -0
  101. package/src/observe/snapshot-metadata.ts +12 -1
  102. package/src/observe/system-trace-android.ts +133 -0
  103. package/src/observe/system-trace.ts +218 -0
  104. package/src/server/common.ts +7 -0
  105. package/src/server/condition-schema.ts +32 -0
  106. package/src/server/debugger-handlers.ts +29 -0
  107. package/src/server/debugger-schema.ts +37 -0
  108. package/src/server/journey-schema.ts +4 -2
  109. package/src/server/readiness-schema.ts +12 -0
  110. package/src/server/system-trace-schema.ts +17 -0
  111. package/src/server/tool-definitions.ts +49 -0
  112. package/src/server/tool-handlers.ts +182 -21
  113. package/src/server-core.ts +3 -3
  114. package/src/server.ts +24 -0
  115. package/src/system/index.ts +7 -5
  116. package/src/types.ts +30 -1
  117. package/src/utils/exec.ts +2 -0
  118. package/src/utils/ios/utils.ts +4 -4
  119. package/src/utils/java.ts +13 -12
  120. package/src/utils/operation-budget.ts +24 -2
  121. package/src/utils/resolve-device.ts +10 -0
  122. package/test/device/automated/debugger/runtime.android.test.ts +203 -0
  123. package/test/device/automated/observe/correlated-evidence.smoke.ts +70 -0
  124. package/test/device/automated/observe/system-trace.smoke.ts +104 -0
  125. package/test/device/manual/interact/conditional_actions.manual.ts +95 -0
  126. package/test/device/manual/interact/modul8_selector_tap.manual.ts +63 -0
  127. package/test/device/manual/interact/readiness.manual.ts +99 -0
  128. package/test/fixtures/debugger-app/MainActivity.java +46 -0
  129. package/test/fixtures/debugger-app/NoLocals.java +7 -0
  130. package/test/fixtures/debugger-app/README.md +13 -0
  131. package/test/fixtures/debugger-app/build.ts +29 -0
  132. package/test/fixtures/readiness-app/MainActivity.java +23 -0
  133. package/test/fixtures/readiness-app/README.md +12 -0
  134. package/test/fixtures/readiness-app/build.ts +37 -0
  135. package/test/fixtures/readiness-app/main.m +27 -0
  136. package/test/helpers/perfetto.ts +59 -0
  137. package/test/unit/debugger/guards.test.ts +46 -0
  138. package/test/unit/debugger/native.test.ts +33 -0
  139. package/test/unit/debugger/sessions.test.ts +83 -0
  140. package/test/unit/observe/android_tree_acquisition.test.ts +2 -0
  141. package/test/unit/observe/capture_debug_snapshot.test.ts +42 -32
  142. package/test/unit/observe/compact_tree_error.test.ts +24 -0
  143. package/test/unit/observe/conditional_snapshots.test.ts +33 -0
  144. package/test/unit/observe/correlated-evidence.test.ts +128 -0
  145. package/test/unit/observe/ios_tree_acquisition.test.ts +2 -0
  146. package/test/unit/observe/readiness.test.ts +41 -0
  147. package/test/unit/observe/readiness_native.test.ts +68 -0
  148. package/test/unit/observe/system-trace-android.test.ts +52 -0
  149. package/test/unit/observe/system-trace.test.ts +166 -0
  150. package/test/unit/server/conditional_actions.test.ts +299 -0
  151. package/test/unit/server/conditional_schema.test.ts +59 -0
  152. package/test/unit/server/correlated-evidence.test.ts +194 -0
  153. package/test/unit/server/readiness.test.ts +170 -0
  154. package/test/unit/server/selector_tap_target.test.ts +79 -0
  155. package/test/unit/server/system-trace.test.ts +64 -0
  156. package/test/unit/server/tap_selector.test.ts +2 -0
  157. package/worker/DebuggerWorker.java +160 -0
  158. package/skills/mobile-debug-tools-usage/SKILL.md +0 -96
  159. /package/{skills → .agents/skills}/README.md +0 -0
  160. /package/{skills → .agents/skills}/mcp-builder/SKILL.md +0 -0
  161. /package/{skills → .agents/skills}/mcp-builder/references/build-flags.md +0 -0
  162. /package/{skills → .agents/skills}/mcp-builder/references/diagnostics-schema.md +0 -0
  163. /package/{skills → .agents/skills}/mcp-builder/references/toolchain-details.md +0 -0
  164. /package/{skills → .agents/skills}/spec-review/SKILL.md +0 -0
  165. /package/{skills → .agents/skills}/spec-review/references/spec-review-checklist.md +0 -0
  166. /package/{skills → .agents/skills}/spec-review/references/spec-review-template.md +0 -0
  167. /package/{skills → .agents/skills}/test-authoring/SKILL.md +0 -0
  168. /package/{skills → .agents/skills}/test-authoring/references/repo-test-layout.md +0 -0
  169. /package/{skills → .agents/skills}/test-authoring/references/test-authoring-workflow.md +0 -0
  170. /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