mobile-debug-mcp 0.28.0 → 0.30.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.md +13 -0
- package/README.md +44 -21
- package/dist/interact/index.js +30 -25
- package/dist/server/tool-definitions.js +1 -1
- package/dist/server/tool-handlers.js +1 -1
- package/dist/server-core.js +12 -2
- package/dist/server.js +5 -3
- package/docs/CHANGELOG.md +9 -0
- package/docs/ROADMAP.md +36 -28
- package/docs/rfcs/013-wait-and-synchronization-reliability.md +890 -0
- package/docs/rfcs/014-actionability-resolution.md +392 -0
- package/docs/specs/mcp-tooling-spec-v1.md +28 -0
- package/docs/tools/interact.md +6 -0
- package/package.json +1 -1
- package/src/interact/index.ts +29 -24
- package/src/server/tool-definitions.ts +1 -1
- package/src/server/tool-handlers.ts +1 -1
- package/src/server-core.ts +17 -1
- package/src/server.ts +5 -3
- package/test/unit/interact/wait_for_ui_change.test.ts +105 -52
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
# RFC 014 — Actionability Resolution
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Draft
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# 1. Summary
|
|
10
|
+
|
|
11
|
+
This RFC defines a deterministic model for resolving whether a UI element is actionable within mobile-debug-mcp.
|
|
12
|
+
|
|
13
|
+
Actionability Resolution ensures that interactions (tap, input, scroll targets, gestures) are only executed against elements that are in a valid and stable state for interaction.
|
|
14
|
+
|
|
15
|
+
It builds directly on:
|
|
16
|
+
|
|
17
|
+
- RFC 013 (Wait and Synchronization Reliability)
|
|
18
|
+
- Stronger State Verification primitives
|
|
19
|
+
- Richer Element Identity model
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
# 2. Relationship to Existing Resolution System (RFC 005 / RFC 007)
|
|
24
|
+
|
|
25
|
+
This RFC refines, but does not replace, the existing resolution pipeline defined in RFC 005 (Resolved Stage) and RFC 007 (Executable Target Selection).
|
|
26
|
+
|
|
27
|
+
Specifically:
|
|
28
|
+
|
|
29
|
+
- RFC 005 defines when an element is considered a valid resolved target
|
|
30
|
+
- RFC 007 defines how executable targets are selected from candidate elements
|
|
31
|
+
- RFC 014 defines a **pre-execution predicate gate**: whether a resolved target is safe to interact with at dispatch time
|
|
32
|
+
|
|
33
|
+
RFC 014 MUST be applied AFTER RFC 005/007 resolution and BEFORE any interaction dispatch occurs.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
# 2. Problem Statement
|
|
38
|
+
|
|
39
|
+
Current interaction failures arise when actions are attempted on elements that are:
|
|
40
|
+
|
|
41
|
+
- not yet fully rendered
|
|
42
|
+
- temporarily disabled during recomposition
|
|
43
|
+
- visually present but not interactable due to transient platform state (composition, layout, or interaction eligibility changes)
|
|
44
|
+
- stale due to snapshot timing issues
|
|
45
|
+
|
|
46
|
+
This leads to:
|
|
47
|
+
|
|
48
|
+
- missed taps
|
|
49
|
+
- retry loops
|
|
50
|
+
- inconsistent automation outcomes
|
|
51
|
+
- non-deterministic test behavior
|
|
52
|
+
|
|
53
|
+
There is currently no single authoritative model defining when an element is safe to interact with.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
# 3. Goals
|
|
58
|
+
|
|
59
|
+
The system should:
|
|
60
|
+
|
|
61
|
+
- Provide a deterministic definition of "actionable"
|
|
62
|
+
- Prevent interactions on unstable UI states
|
|
63
|
+
- Reduce retries caused by transient UI conditions
|
|
64
|
+
- Integrate with stabilization semantics from RFC 013
|
|
65
|
+
- Support both Android and iOS UI models
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
# 4. Non-Goals
|
|
70
|
+
|
|
71
|
+
This RFC does NOT:
|
|
72
|
+
|
|
73
|
+
- define gesture recognition internals
|
|
74
|
+
- implement visual computer vision occlusion detection
|
|
75
|
+
- guarantee backend readiness
|
|
76
|
+
- replace synchronization primitives from RFC 013
|
|
77
|
+
- define visual occlusion detection; interaction eligibility is derived from platform clickable/interactable semantics, not vision-based inference
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
# 5. Definition: Actionability
|
|
82
|
+
|
|
83
|
+
An element is considered **actionable** if ALL of the following conditions are satisfied:
|
|
84
|
+
|
|
85
|
+
## 5.1 Structural Validity
|
|
86
|
+
|
|
87
|
+
- Element exists in the current UI hierarchy snapshot
|
|
88
|
+
- Element identity is stable (per Richer Element Identity model)
|
|
89
|
+
|
|
90
|
+
### Identity Source of Truth
|
|
91
|
+
|
|
92
|
+
Element identity MUST be derived using the following priority order:
|
|
93
|
+
|
|
94
|
+
1. `stable_id` (primary source from runtime)
|
|
95
|
+
2. `elementId` deterministic hash (fallback)
|
|
96
|
+
3. recomputed semantic node signature (last resort)
|
|
97
|
+
|
|
98
|
+
If identity cannot be resolved consistently across snapshots, the element MUST be treated as non-actionable due to instability.
|
|
99
|
+
|
|
100
|
+
## 5.2 Visibility
|
|
101
|
+
|
|
102
|
+
- Element is visible within the current viewport OR scrollable container
|
|
103
|
+
- Element is not explicitly hidden (e.g. visibility = gone / hidden)
|
|
104
|
+
|
|
105
|
+
## 5.3 Enabled State
|
|
106
|
+
|
|
107
|
+
- Element is not disabled
|
|
108
|
+
- Element accepts interaction for the requested action type
|
|
109
|
+
- Must be interpreted relative to action_type (see Section 6.4)
|
|
110
|
+
|
|
111
|
+
## 5.4 Stability Requirement
|
|
112
|
+
|
|
113
|
+
- UI must be in a "stable" state per RFC 013 stabilization rules
|
|
114
|
+
- No synchronization-relevant mutation is currently active
|
|
115
|
+
|
|
116
|
+
## 5.5 Interaction Eligibility
|
|
117
|
+
|
|
118
|
+
- Element is not in a transitional animation state that blocks interaction
|
|
119
|
+
- Element is not temporarily detached or reparenting in the hierarchy
|
|
120
|
+
- Element MUST satisfy runtime proxy interactability derived only from visible, enabled, and clickable fields
|
|
121
|
+
- No hit-test, overlay, or occlusion guarantees are assumed
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
# 6. Actionability Evaluation Model (Runtime Contract)
|
|
126
|
+
|
|
127
|
+
Actionability is evaluated using runtime-provided fields from the resolved element and snapshot system.
|
|
128
|
+
|
|
129
|
+
## 6.1 Predicate Definition
|
|
130
|
+
|
|
131
|
+
```text
|
|
132
|
+
is_actionable(element, snapshot, normalized_action_type) =
|
|
133
|
+
exists(element)
|
|
134
|
+
AND snapshot.stable == true
|
|
135
|
+
AND element.visible == true
|
|
136
|
+
AND element.enabled == true
|
|
137
|
+
AND normalized_action_type IS NOT NULL
|
|
138
|
+
AND actionability_by_type(normalized_action_type, element)
|
|
139
|
+
AND identity_is_stable(element)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## 6.2 Field Mapping
|
|
143
|
+
|
|
144
|
+
| Predicate Component | Source Field |
|
|
145
|
+
|--------------------|-------------|
|
|
146
|
+
| exists(element) | element presence in resolved tree |
|
|
147
|
+
| snapshot.stable | RFC 013 stabilization state |
|
|
148
|
+
| element.visible | visible / computed visibility flag |
|
|
149
|
+
| element.enabled | enabled / interactable flag |
|
|
150
|
+
| actionability_by_type(action_type, element) | derived runtime interaction capability from visible, enabled, clickable proxy signals (no hit-test dependency) |
|
|
151
|
+
| identity_is_stable | stable_id or fallback identity resolution |
|
|
152
|
+
|
|
153
|
+
## 6.3 Identity Stability Rule
|
|
154
|
+
|
|
155
|
+
Identity is considered stable if `stable_id` remains unchanged across the latest two snapshots.
|
|
156
|
+
|
|
157
|
+
If identity changes, the element MUST be re-resolved before actionability can be evaluated.
|
|
158
|
+
|
|
159
|
+
## 6.4 Action Type Applicability (Core Contract)
|
|
160
|
+
|
|
161
|
+
Action type is a PRIMARY dimension of actionability and MUST be evaluated explicitly for every interaction.
|
|
162
|
+
|
|
163
|
+
The predicate `actionability_by_type(action_type, element)` is not optional and defines interaction correctness.
|
|
164
|
+
|
|
165
|
+
If the caller omits `action_type`, implementations MUST evaluate Tap / Click semantics.
|
|
166
|
+
Before evaluation, implementations MUST normalize the effective action type to a concrete value.
|
|
167
|
+
|
|
168
|
+
Each action type defines strict eligibility constraints:
|
|
169
|
+
|
|
170
|
+
### Tap / Click (Default Interaction Model)
|
|
171
|
+
- Requires: element.clickable == true
|
|
172
|
+
- Requires: element.visible == true
|
|
173
|
+
- Requires: element.enabled == true
|
|
174
|
+
- This is the DEFAULT action model when action_type is unspecified
|
|
175
|
+
|
|
176
|
+
### Input / Text Entry
|
|
177
|
+
- Requires: element.enabled == true
|
|
178
|
+
- Requires: element.focusable == true OR platform equivalent input capability
|
|
179
|
+
- Clickable is NOT required
|
|
180
|
+
|
|
181
|
+
### Scroll Targeting
|
|
182
|
+
- Requires: element is within a scrollable container
|
|
183
|
+
- Requires: element.visible == true OR can be brought into viewport
|
|
184
|
+
- Enabled state is NOT required
|
|
185
|
+
|
|
186
|
+
### Gesture (Swipe / Drag)
|
|
187
|
+
- Requires: element exists in stable snapshot
|
|
188
|
+
- Requires: element.visible == true
|
|
189
|
+
- Requires: no active animation or transition state that blocks interaction
|
|
190
|
+
|
|
191
|
+
### Contract Rule
|
|
192
|
+
|
|
193
|
+
Implementations MUST treat the effective action type as a required discriminator in all actionability evaluations.
|
|
194
|
+
|
|
195
|
+
### Determinism Rule
|
|
196
|
+
|
|
197
|
+
Action type evaluation is fully deterministic.
|
|
198
|
+
|
|
199
|
+
All constraints for a given action type MUST be evaluated as a single conjunctive predicate.
|
|
200
|
+
|
|
201
|
+
No constraint MAY override another constraint. No priority ordering between fields is permitted.
|
|
202
|
+
|
|
203
|
+
Evaluation MUST produce identical results for identical inputs (element, snapshot, action_type).
|
|
204
|
+
|
|
205
|
+
## 6.5 Deterministic Evaluation Guarantees
|
|
206
|
+
|
|
207
|
+
Actionability evaluation is a pure function of:
|
|
208
|
+
|
|
209
|
+
- element
|
|
210
|
+
- snapshot
|
|
211
|
+
- action_type
|
|
212
|
+
|
|
213
|
+
The evaluation MUST NOT depend on external or hidden state.
|
|
214
|
+
|
|
215
|
+
The system MUST guarantee:
|
|
216
|
+
|
|
217
|
+
- identical inputs produce identical outputs
|
|
218
|
+
- no temporal or execution-order dependency affects results
|
|
219
|
+
- all predicates are evaluated in a single logical context
|
|
220
|
+
|
|
221
|
+
Short-circuit evaluation is permitted for performance but MUST NOT change final outcome semantics.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
# 7. Resolution Lifecycle
|
|
226
|
+
|
|
227
|
+
Actionability MUST be evaluated in the following sequence:
|
|
228
|
+
|
|
229
|
+
1. Retrieve latest UI snapshot
|
|
230
|
+
2. Verify snapshot is stable (RFC 013)
|
|
231
|
+
3. Resolve element identity
|
|
232
|
+
4. Evaluate visibility constraints
|
|
233
|
+
5. Evaluate enabled state
|
|
234
|
+
6. Evaluate interaction readiness
|
|
235
|
+
7. Return final boolean result
|
|
236
|
+
|
|
237
|
+
If any step fails, the element MUST be considered non-actionable.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
# 8. Interaction Guardrail
|
|
242
|
+
|
|
243
|
+
Actions MUST NOT be executed unless actionability resolution returns true.
|
|
244
|
+
|
|
245
|
+
If actionability is false:
|
|
246
|
+
|
|
247
|
+
- system MAY retry after waiting for stabilization
|
|
248
|
+
- system MUST NOT force interaction
|
|
249
|
+
- system MAY re-resolve snapshot
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
# 9. Failure Modes
|
|
254
|
+
|
|
255
|
+
## 9.1 Transient Non-Actionability
|
|
256
|
+
|
|
257
|
+
Caused by:
|
|
258
|
+
|
|
259
|
+
- recomposition
|
|
260
|
+
- animation transitions
|
|
261
|
+
- delayed rendering
|
|
262
|
+
|
|
263
|
+
Mitigation:
|
|
264
|
+
|
|
265
|
+
- rely on RFC 013 stabilization
|
|
266
|
+
- re-evaluate after wait cycle
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## 9.2 Stale Snapshot
|
|
271
|
+
|
|
272
|
+
Caused by:
|
|
273
|
+
|
|
274
|
+
- outdated hierarchy
|
|
275
|
+
- missed mutation events
|
|
276
|
+
|
|
277
|
+
Mitigation:
|
|
278
|
+
|
|
279
|
+
- require fresh snapshot before evaluation
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## 9.3 False Positives (appears actionable but is not)
|
|
284
|
+
|
|
285
|
+
Caused by:
|
|
286
|
+
|
|
287
|
+
- proxy signal mismatch (e.g. element reports clickable/visible but runtime rejects interaction via proxy constraints)
|
|
288
|
+
|
|
289
|
+
Mitigation:
|
|
290
|
+
|
|
291
|
+
- stricter validation of visible/enabled/clickable proxy consistency
|
|
292
|
+
- re-resolution of element state before evaluation
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
# 10. Platform Considerations
|
|
297
|
+
|
|
298
|
+
## Android
|
|
299
|
+
|
|
300
|
+
- View visibility and enabled-state are primary signals
|
|
301
|
+
- Touch target bounds SHOULD be respected
|
|
302
|
+
|
|
303
|
+
## iOS
|
|
304
|
+
|
|
305
|
+
- UIKit and SwiftUI state transitions may delay interaction readiness
|
|
306
|
+
- Accessibility tree is primary signal for actionability
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
# 11. Integration with RFC 013
|
|
311
|
+
|
|
312
|
+
Actionability Resolution depends on:
|
|
313
|
+
|
|
314
|
+
- Stable snapshot guarantee
|
|
315
|
+
- Synchronization-relevant mutation rules
|
|
316
|
+
|
|
317
|
+
It MUST NOT evaluate elements from unstable snapshots.
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
# 12. Telemetry
|
|
322
|
+
|
|
323
|
+
Systems SHOULD track:
|
|
324
|
+
|
|
325
|
+
- actionability rejection rate
|
|
326
|
+
- retry frequency after failed actionability
|
|
327
|
+
- time-to-actionable per element
|
|
328
|
+
- false-positive interaction attempts
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
# 13. Rollout Strategy
|
|
333
|
+
|
|
334
|
+
## Phase 1
|
|
335
|
+
|
|
336
|
+
- Basic structural + visibility checks
|
|
337
|
+
|
|
338
|
+
## Phase 2
|
|
339
|
+
|
|
340
|
+
- Stability integration (RFC 013)
|
|
341
|
+
|
|
342
|
+
## Phase 3
|
|
343
|
+
|
|
344
|
+
- Platform-specific interaction heuristics
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
# 14. Dependencies
|
|
349
|
+
|
|
350
|
+
## Depends On
|
|
351
|
+
|
|
352
|
+
- RFC 013 — Wait and Synchronization Reliability
|
|
353
|
+
- Stronger State Verification
|
|
354
|
+
- Richer Element Identity
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
# 14.1 Acceptance Criteria
|
|
359
|
+
|
|
360
|
+
An implementation of RFC 014 is considered correct if:
|
|
361
|
+
|
|
362
|
+
- Disabled elements never pass actionability checks
|
|
363
|
+
- Elements in unstable snapshots are never actionable
|
|
364
|
+
- Identity changes force re-resolution before interaction
|
|
365
|
+
- Proxy interaction eligibility blocked elements are rejected
|
|
366
|
+
- All failures produce a defined failure code
|
|
367
|
+
- Actionability is only evaluated AFTER RFC 005/007 resolution
|
|
368
|
+
|
|
369
|
+
# 15. Failure Codes (Actionability Gate)
|
|
370
|
+
|
|
371
|
+
## Failure Code Derivation Matrix
|
|
372
|
+
|
|
373
|
+
Failure codes MUST be derived directly from predicate evaluation results:
|
|
374
|
+
|
|
375
|
+
| Predicate Failure Condition | Failure Code |
|
|
376
|
+
|----------------------------|-------------|
|
|
377
|
+
| element does not exist in snapshot | NOT_FOUND |
|
|
378
|
+
| element.visible == false | NOT_VISIBLE |
|
|
379
|
+
| element.enabled == false | NOT_ENABLED |
|
|
380
|
+
| snapshot.stable == false | NOT_STABLE |
|
|
381
|
+
| identity_is_stable == false | IDENTITY_UNSTABLE |
|
|
382
|
+
| actionability_by_type(...) == false | NOT_ELIGIBLE |
|
|
383
|
+
|
|
384
|
+
These are the only supported actionability failure codes.
|
|
385
|
+
|
|
386
|
+
Implementations SHOULD return the structured code together with a human-readable reason.
|
|
387
|
+
|
|
388
|
+
# 16. Conclusion
|
|
389
|
+
|
|
390
|
+
Actionability Resolution provides the final gate before UI interaction execution.
|
|
391
|
+
|
|
392
|
+
It ensures that all interactions are performed only on elements that are structurally valid, stable, visible, and interaction-ready, significantly reducing non-deterministic automation behavior.
|
|
@@ -194,6 +194,34 @@ Examples:
|
|
|
194
194
|
- `success` indicates condition met or resolution succeeded
|
|
195
195
|
- `success` does NOT indicate outcome correctness
|
|
196
196
|
|
|
197
|
+
### 6.4 `wait_for_ui_change`
|
|
198
|
+
|
|
199
|
+
`wait_for_ui_change` synchronizes on observable in-place UI mutation.
|
|
200
|
+
|
|
201
|
+
Inputs:
|
|
202
|
+
|
|
203
|
+
- `platform?: "android" | "ios"`
|
|
204
|
+
- `deviceId?: string`
|
|
205
|
+
- `timeout_ms?: number`
|
|
206
|
+
- `stability_window_ms?: number` (default: `300`)
|
|
207
|
+
- `expected_change?: "hierarchy_diff" | "text_change" | "state_change"`
|
|
208
|
+
|
|
209
|
+
Required semantics:
|
|
210
|
+
|
|
211
|
+
- success means a qualifying UI mutation was observed and remained stable for a full `stability_window_ms`
|
|
212
|
+
- stabilization MUST reset whenever a synchronization-relevant mutation is observed
|
|
213
|
+
- the stabilization window MUST be measured from the most recent qualifying mutation
|
|
214
|
+
- the implementation MUST treat as synchronization-relevant: element addition or removal, visibility changes, enabled-state changes, navigation transitions, text or content-description changes, subtree structure mutation, and semantic accessibility tree mutation
|
|
215
|
+
- the implementation MUST NOT treat as synchronization-relevant: animation frame updates, layout-only jitter, opacity-only visual transitions, and non-semantic rendering updates
|
|
216
|
+
- partial convergence MUST NOT be reported as success
|
|
217
|
+
- timeout MAY return the last observed state, but MUST NOT report stable convergence
|
|
218
|
+
|
|
219
|
+
Guidance:
|
|
220
|
+
|
|
221
|
+
- prefer `wait_for_screen_change` for navigation transitions
|
|
222
|
+
- prefer `wait_for_ui_change` for in-place updates and recomposition-style changes
|
|
223
|
+
- follow with `expect_*` when the expected final state is known
|
|
224
|
+
|
|
197
225
|
## 7. Failure Semantics
|
|
198
226
|
|
|
199
227
|
### 7.1 Canonical Codes
|
package/docs/tools/interact.md
CHANGED
|
@@ -182,6 +182,12 @@ Capabilities:
|
|
|
182
182
|
- waits for hierarchy, text, or state deltas
|
|
183
183
|
- uses snapshot revision metadata when available
|
|
184
184
|
- confirms the change remains stable before returning success
|
|
185
|
+
- defaults `stability_window_ms` to `300`
|
|
186
|
+
|
|
187
|
+
Mutation rules:
|
|
188
|
+
|
|
189
|
+
- synchronization-relevant: element addition or removal, visibility changes, enabled-state changes, navigation transitions, text or content-description changes, subtree structure mutation, semantic accessibility tree mutation
|
|
190
|
+
- not synchronization-relevant: animation frame updates, layout-only jitter, opacity-only visual transitions, non-semantic rendering updates
|
|
185
191
|
|
|
186
192
|
Guidance:
|
|
187
193
|
|
package/package.json
CHANGED
package/src/interact/index.ts
CHANGED
|
@@ -332,6 +332,10 @@ export class ToolsInteract {
|
|
|
332
332
|
return null
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
+
private static _uiChangeSignaturesEqual(left: UiChangeSignatureSet, right: UiChangeSignatureSet): boolean {
|
|
336
|
+
return left.hierarchy === right.hierarchy && left.text === right.text && left.state === right.state
|
|
337
|
+
}
|
|
338
|
+
|
|
335
339
|
private static _resolvedTargetFromElement(
|
|
336
340
|
elementId: string,
|
|
337
341
|
element: UiElement,
|
|
@@ -2016,7 +2020,7 @@ export class ToolsInteract {
|
|
|
2016
2020
|
platform,
|
|
2017
2021
|
deviceId,
|
|
2018
2022
|
timeout_ms = 60000,
|
|
2019
|
-
stability_window_ms =
|
|
2023
|
+
stability_window_ms = 300,
|
|
2020
2024
|
expected_change
|
|
2021
2025
|
}: {
|
|
2022
2026
|
platform?: 'android' | 'ios',
|
|
@@ -2027,10 +2031,13 @@ export class ToolsInteract {
|
|
|
2027
2031
|
}): Promise<WaitForUIChangeResponse> {
|
|
2028
2032
|
const start = Date.now()
|
|
2029
2033
|
const pollIntervalMs = 300
|
|
2030
|
-
const stabilityWindow = Math.max(0, typeof stability_window_ms === 'number' ? stability_window_ms :
|
|
2034
|
+
const stabilityWindow = Math.max(0, typeof stability_window_ms === 'number' ? stability_window_ms : 300)
|
|
2031
2035
|
let baseline: UiChangeSignatureSet | null = null
|
|
2032
2036
|
let lastObservedRevision: number | null = null
|
|
2033
2037
|
let lastLoadingState: any = null
|
|
2038
|
+
let candidateSignatures: UiChangeSignatureSet | null = null
|
|
2039
|
+
let candidateObservedChange: 'hierarchy_diff' | 'text_change' | 'state_change' | null = null
|
|
2040
|
+
let candidateSinceMs: number | null = null
|
|
2034
2041
|
|
|
2035
2042
|
while (Date.now() - start < timeout_ms) {
|
|
2036
2043
|
try {
|
|
@@ -2044,31 +2051,29 @@ export class ToolsInteract {
|
|
|
2044
2051
|
} else {
|
|
2045
2052
|
const observedChange = ToolsInteract._matchesUiChange(expected_change, baseline, signatures)
|
|
2046
2053
|
if (observedChange) {
|
|
2047
|
-
if (
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
const confirmChange = ToolsInteract._matchesUiChange(expected_change, baseline, confirmSignatures)
|
|
2052
|
-
if (!confirmChange || confirmSignatures.hierarchy !== signatures.hierarchy || confirmSignatures.text !== signatures.text || confirmSignatures.state !== signatures.state) {
|
|
2053
|
-
lastObservedRevision = typeof confirmTree?.snapshot_revision === 'number' ? confirmTree.snapshot_revision : lastObservedRevision
|
|
2054
|
-
lastLoadingState = confirmTree?.loading_state ?? lastLoadingState
|
|
2055
|
-
await new Promise(resolve => setTimeout(resolve, pollIntervalMs))
|
|
2056
|
-
continue
|
|
2057
|
-
}
|
|
2058
|
-
lastObservedRevision = typeof confirmTree?.snapshot_revision === 'number' ? confirmTree.snapshot_revision : lastObservedRevision
|
|
2059
|
-
lastLoadingState = confirmTree?.loading_state ?? lastLoadingState
|
|
2054
|
+
if (!candidateSignatures || !ToolsInteract._uiChangeSignaturesEqual(candidateSignatures, signatures) || candidateObservedChange !== observedChange) {
|
|
2055
|
+
candidateSignatures = signatures
|
|
2056
|
+
candidateObservedChange = observedChange
|
|
2057
|
+
candidateSinceMs = Date.now()
|
|
2060
2058
|
}
|
|
2061
2059
|
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2060
|
+
const stableForMs = candidateSinceMs === null ? 0 : Date.now() - candidateSinceMs
|
|
2061
|
+
if (stabilityWindow === 0 || stableForMs >= stabilityWindow) {
|
|
2062
|
+
return {
|
|
2063
|
+
success: true,
|
|
2064
|
+
observed_change: candidateObservedChange ?? observedChange,
|
|
2065
|
+
snapshot_revision: lastObservedRevision ?? undefined,
|
|
2066
|
+
timeout: false,
|
|
2067
|
+
elapsed_ms: Date.now() - start,
|
|
2068
|
+
expected_change,
|
|
2069
|
+
loading_state: lastLoadingState ?? null,
|
|
2070
|
+
reason: 'UI change observed'
|
|
2071
|
+
}
|
|
2071
2072
|
}
|
|
2073
|
+
} else {
|
|
2074
|
+
candidateSignatures = null
|
|
2075
|
+
candidateObservedChange = null
|
|
2076
|
+
candidateSinceMs = null
|
|
2072
2077
|
}
|
|
2073
2078
|
}
|
|
2074
2079
|
} catch {
|
|
@@ -391,7 +391,7 @@ Failure Handling:
|
|
|
391
391
|
deviceId: { type: 'string', description: 'Optional device id/udid to target' },
|
|
392
392
|
expected_change: { type: 'string', enum: ['hierarchy_diff', 'text_change', 'state_change'], description: 'Optional type of UI change to wait for' },
|
|
393
393
|
timeout_ms: { type: 'number', description: 'Timeout in ms to wait for change (default 60000)', default: 60000 },
|
|
394
|
-
stability_window_ms: { type: 'number', description: 'How long the change must remain stable before success (default
|
|
394
|
+
stability_window_ms: { type: 'number', description: 'How long the change must remain stable before success (default 300)', default: 300 }
|
|
395
395
|
}
|
|
396
396
|
}
|
|
397
397
|
},
|
|
@@ -319,7 +319,7 @@ async function handleWaitForUIChange(args: ToolCallArgs) {
|
|
|
319
319
|
const platform = getStringArg(args, 'platform') as PlatformArg | undefined
|
|
320
320
|
const deviceId = getStringArg(args, 'deviceId')
|
|
321
321
|
const timeout_ms = getNumberArg(args, 'timeout_ms') ?? 60000
|
|
322
|
-
const stability_window_ms = getNumberArg(args, 'stability_window_ms') ??
|
|
322
|
+
const stability_window_ms = getNumberArg(args, 'stability_window_ms') ?? 300
|
|
323
323
|
const expected_change = getStringArg(args, 'expected_change') as 'hierarchy_diff' | 'text_change' | 'state_change' | undefined
|
|
324
324
|
const res = await ToolsInteract.waitForUIChangeHandler({ platform, deviceId, timeout_ms, stability_window_ms, expected_change })
|
|
325
325
|
return wrapResponse(res)
|
package/src/server-core.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
|
|
2
2
|
import type { SchemaOutput } from '@modelcontextprotocol/sdk/server/zod-compat.js'
|
|
3
3
|
import {
|
|
4
|
+
ListResourcesRequestSchema,
|
|
5
|
+
ListResourceTemplatesRequestSchema,
|
|
6
|
+
ReadResourceRequestSchema,
|
|
4
7
|
ListToolsRequestSchema,
|
|
5
8
|
CallToolRequestSchema
|
|
6
9
|
} from '@modelcontextprotocol/sdk/types.js'
|
|
@@ -13,7 +16,7 @@ export { wrapResponse, toolDefinitions, handleToolCall }
|
|
|
13
16
|
|
|
14
17
|
export const serverInfo = {
|
|
15
18
|
name: 'mobile-debug-mcp',
|
|
16
|
-
version: '0.
|
|
19
|
+
version: '0.30.0'
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
export function createServer() {
|
|
@@ -21,11 +24,24 @@ export function createServer() {
|
|
|
21
24
|
serverInfo,
|
|
22
25
|
{
|
|
23
26
|
capabilities: {
|
|
27
|
+
resources: {},
|
|
24
28
|
tools: {}
|
|
25
29
|
}
|
|
26
30
|
}
|
|
27
31
|
)
|
|
28
32
|
|
|
33
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
34
|
+
resources: []
|
|
35
|
+
}))
|
|
36
|
+
|
|
37
|
+
server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
|
|
38
|
+
resourceTemplates: []
|
|
39
|
+
}))
|
|
40
|
+
|
|
41
|
+
server.setRequestHandler(ReadResourceRequestSchema, async () => ({
|
|
42
|
+
contents: []
|
|
43
|
+
}))
|
|
44
|
+
|
|
29
45
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
30
46
|
tools: toolDefinitions
|
|
31
47
|
}))
|
package/src/server.ts
CHANGED
|
@@ -5,9 +5,11 @@ import { getSystemStatus } from './system/index.js'
|
|
|
5
5
|
|
|
6
6
|
const server = createServer()
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
if (process.env.MOBILE_DEBUG_MCP_STARTUP_HEALTHCHECK === '1') {
|
|
9
|
+
getSystemStatus().then((res) => {
|
|
10
|
+
console.info('[startup] system status summary:', { adb: res.adbAvailable, ios: res.iosAvailable, devices: res.devices, iosDevices: res.iosDevices })
|
|
11
|
+
}).catch((e) => console.warn('[startup] healthcheck failed:', e instanceof Error ? e.message : String(e)))
|
|
12
|
+
}
|
|
11
13
|
|
|
12
14
|
const transport = new StdioServerTransport()
|
|
13
15
|
|