mobile-debug-mcp 0.24.8 → 0.25.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/README.md +1 -1
- package/dist/interact/index.js +213 -3
- package/dist/observe/ios.js +56 -2
- package/dist/server/common.js +2 -1
- package/dist/server/tool-definitions.js +55 -0
- package/dist/server/tool-handlers.js +17 -0
- package/dist/server-core.js +1 -1
- package/dist/utils/android/utils.js +67 -1
- package/docs/CHANGELOG.md +3 -0
- package/docs/ROADMAP.md +388 -0
- package/docs/rfcs/001-state-verification.md +452 -0
- package/docs/specs/mcp-tooling-spec-v1.md +4 -0
- package/docs/tools/interact.md +25 -0
- package/docs/tools/observe.md +2 -1
- package/package.json +1 -1
- package/src/interact/index.ts +240 -3
- package/src/observe/ios.ts +62 -3
- package/src/server/common.ts +2 -1
- package/src/server/tool-definitions.ts +55 -0
- package/src/server/tool-handlers.ts +18 -0
- package/src/server-core.ts +1 -1
- package/src/types.ts +41 -0
- package/src/utils/android/utils.ts +78 -14
- package/test/unit/observe/state_extraction.test.ts +43 -0
- package/test/unit/server/response_shapes.test.ts +40 -2
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
import { traverseNode } from '../../../src/utils/android/utils.js'
|
|
3
|
+
import { traverseIDBNode } from '../../../src/observe/ios.js'
|
|
4
|
+
|
|
5
|
+
async function run() {
|
|
6
|
+
const androidElements: any[] = []
|
|
7
|
+
traverseNode({
|
|
8
|
+
'@_class': 'android.widget.SeekBar',
|
|
9
|
+
'@_text': '',
|
|
10
|
+
'@_content-desc': 'Duration',
|
|
11
|
+
'@_clickable': 'true',
|
|
12
|
+
'@_enabled': 'true',
|
|
13
|
+
'@_selected': 'true',
|
|
14
|
+
'@_progress': '7',
|
|
15
|
+
'@_max': '14',
|
|
16
|
+
'@_bounds': '[0,0][200,40]'
|
|
17
|
+
}, androidElements)
|
|
18
|
+
|
|
19
|
+
assert.strictEqual(androidElements.length, 1)
|
|
20
|
+
assert.deepStrictEqual(androidElements[0].state?.selected, 'Duration')
|
|
21
|
+
assert.strictEqual(androidElements[0].state?.raw_value, 7)
|
|
22
|
+
assert.strictEqual(androidElements[0].state?.value, 50)
|
|
23
|
+
assert.deepStrictEqual(androidElements[0].state?.value_range, { min: 0, max: 14 })
|
|
24
|
+
|
|
25
|
+
const iosElements: any[] = []
|
|
26
|
+
traverseIDBNode({
|
|
27
|
+
AXElementType: 'Slider',
|
|
28
|
+
AXLabel: 'Playback speed',
|
|
29
|
+
AXValue: '0.75',
|
|
30
|
+
AXTraits: ['UIAccessibilityTraitAdjustable']
|
|
31
|
+
}, iosElements)
|
|
32
|
+
|
|
33
|
+
assert.strictEqual(iosElements.length, 1)
|
|
34
|
+
assert.strictEqual(iosElements[0].state?.value, 75)
|
|
35
|
+
assert.strictEqual(iosElements[0].state?.raw_value, 0.75)
|
|
36
|
+
|
|
37
|
+
console.log('state extraction tests passed')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
run().catch((error) => {
|
|
41
|
+
console.error(error)
|
|
42
|
+
process.exit(1)
|
|
43
|
+
})
|
|
@@ -12,6 +12,7 @@ async function run() {
|
|
|
12
12
|
const originalTapHandler = (ToolsInteract as any).tapHandler
|
|
13
13
|
const originalExpectScreenHandler = (ToolsInteract as any).expectScreenHandler
|
|
14
14
|
const originalExpectElementVisibleHandler = (ToolsInteract as any).expectElementVisibleHandler
|
|
15
|
+
const originalExpectStateHandler = (ToolsInteract as any).expectStateHandler
|
|
15
16
|
const originalStartApp = AndroidManage.prototype.startApp
|
|
16
17
|
const originalCaptureScreenshotHandler = (ToolsObserve as any).captureScreenshotHandler
|
|
17
18
|
const originalGetUITreeHandler = (ToolsObserve as any).getUITreeHandler
|
|
@@ -130,8 +131,8 @@ async function run() {
|
|
|
130
131
|
selector: { text: 'Ready' },
|
|
131
132
|
element_id: 'el_ready',
|
|
132
133
|
expected_condition: 'visible',
|
|
133
|
-
element: { elementId: 'el_ready', text: 'Ready', resource_id: null, accessibility_id: null, class: 'TextView', bounds: [0, 0, 10, 10], index: 0 },
|
|
134
|
-
observed: { status: 'success', matched_count: 1, condition_satisfied: true, selected_index: 0, last_matched_element: { elementId: 'el_ready', text: 'Ready', resource_id: null, accessibility_id: null, class: 'TextView', bounds: [0, 0, 10, 10], index: 0 } },
|
|
134
|
+
element: { elementId: 'el_ready', text: 'Ready', resource_id: null, accessibility_id: null, class: 'TextView', bounds: [0, 0, 10, 10], index: 0, state: { enabled: true } },
|
|
135
|
+
observed: { status: 'success', matched_count: 1, condition_satisfied: true, selected_index: 0, last_matched_element: { elementId: 'el_ready', text: 'Ready', resource_id: null, accessibility_id: null, class: 'TextView', bounds: [0, 0, 10, 10], index: 0, state: { enabled: true } } },
|
|
135
136
|
reason: 'selector is visible'
|
|
136
137
|
})
|
|
137
138
|
|
|
@@ -141,6 +142,42 @@ async function run() {
|
|
|
141
142
|
assert.strictEqual(expectElementPayload.element_id, 'el_ready')
|
|
142
143
|
assert.strictEqual(expectElementPayload.expected_condition, 'visible')
|
|
143
144
|
|
|
145
|
+
;(ToolsObserve as any).getUITreeHandler = async () => ({
|
|
146
|
+
device: { platform: 'android', id: 'mock', osVersion: '14', model: 'Pixel', simulator: true },
|
|
147
|
+
resolution: { width: 1080, height: 2400 },
|
|
148
|
+
elements: [{
|
|
149
|
+
text: 'Notifications',
|
|
150
|
+
depth: 0,
|
|
151
|
+
center: { x: 50, y: 20 },
|
|
152
|
+
state: { checked: true, selected: 'Notifications' }
|
|
153
|
+
}]
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
;(ToolsInteract as any).expectStateHandler = async () => ({
|
|
157
|
+
success: true,
|
|
158
|
+
selector: { text: 'Notifications' },
|
|
159
|
+
element_id: 'el_notifications',
|
|
160
|
+
expected_state: { property: 'checked', expected: true },
|
|
161
|
+
element: {
|
|
162
|
+
elementId: 'el_notifications',
|
|
163
|
+
text: 'Notifications',
|
|
164
|
+
resource_id: null,
|
|
165
|
+
accessibility_id: null,
|
|
166
|
+
class: 'Switch',
|
|
167
|
+
bounds: [0, 0, 10, 10],
|
|
168
|
+
index: 0,
|
|
169
|
+
state: { checked: true, selected: 'Notifications' }
|
|
170
|
+
},
|
|
171
|
+
observed_state: { property: 'checked', value: true, raw_value: true },
|
|
172
|
+
reason: 'checked matches expected value'
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
const expectStateResponse = await handleToolCall('expect_state', { selector: { text: 'Notifications' }, property: 'checked', expected: true })
|
|
176
|
+
const expectStatePayload = JSON.parse((expectStateResponse as any).content[0].text)
|
|
177
|
+
assert.strictEqual(expectStatePayload.success, true)
|
|
178
|
+
assert.strictEqual(expectStatePayload.expected_state.property, 'checked')
|
|
179
|
+
assert.strictEqual(expectStatePayload.observed_state.value, true)
|
|
180
|
+
|
|
144
181
|
;(ToolsInteract as any).tapHandler = async () => {
|
|
145
182
|
throw new Error('boom')
|
|
146
183
|
}
|
|
@@ -234,6 +271,7 @@ async function run() {
|
|
|
234
271
|
;(ToolsInteract as any).tapHandler = originalTapHandler
|
|
235
272
|
;(ToolsInteract as any).expectScreenHandler = originalExpectScreenHandler
|
|
236
273
|
;(ToolsInteract as any).expectElementVisibleHandler = originalExpectElementVisibleHandler
|
|
274
|
+
;(ToolsInteract as any).expectStateHandler = originalExpectStateHandler
|
|
237
275
|
AndroidManage.prototype.startApp = originalStartApp
|
|
238
276
|
;(ToolsObserve as any).captureScreenshotHandler = originalCaptureScreenshotHandler
|
|
239
277
|
;(ToolsObserve as any).getUITreeHandler = originalGetUITreeHandler
|