mobile-debug-mcp 0.23.0 → 0.24.1

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.
@@ -0,0 +1,365 @@
1
+ import type {
2
+ InstallAppResponse,
3
+ ResetAppDataResponse,
4
+ TerminateAppResponse
5
+ } from '../types.js'
6
+ import { AndroidManage, iOSManage, ToolsManage } from '../manage/index.js'
7
+ import { ToolsInteract } from '../interact/index.js'
8
+ import { ToolsObserve } from '../observe/index.js'
9
+ import { classifyActionOutcome } from '../interact/classify.js'
10
+ import { ToolsNetwork } from '../network/index.js'
11
+ import { getSystemStatus } from '../system/index.js'
12
+ import {
13
+ buildActionExecutionResult,
14
+ captureActionFingerprint,
15
+ inferGenericFailure,
16
+ inferScrollFailure,
17
+ ToolCallArgs,
18
+ ToolHandler,
19
+ wrapResponse
20
+ } from './common.js'
21
+
22
+ async function handleStartApp(args: ToolCallArgs) {
23
+ const { platform, appId, deviceId } = args as any
24
+ const uiFingerprintBefore = await captureActionFingerprint(platform, deviceId)
25
+ ToolsNetwork.notifyActionStart()
26
+ const res = await (platform === 'android' ? new AndroidManage().startApp(appId, deviceId) : new iOSManage().startApp(appId, deviceId))
27
+ const uiFingerprintAfter = await captureActionFingerprint(platform, deviceId)
28
+ return wrapResponse(buildActionExecutionResult({
29
+ actionType: 'start_app',
30
+ selector: { appId },
31
+ success: !!res.appStarted,
32
+ uiFingerprintBefore,
33
+ uiFingerprintAfter,
34
+ failure: res.appStarted ? undefined : inferGenericFailure((res as any).error)
35
+ }))
36
+ }
37
+
38
+ async function handleTerminateApp(args: ToolCallArgs) {
39
+ const { platform, appId, deviceId } = args as any
40
+ const res = await (platform === 'android' ? new AndroidManage().terminateApp(appId, deviceId) : new iOSManage().terminateApp(appId, deviceId))
41
+ const response: TerminateAppResponse = { device: res.device, appTerminated: res.appTerminated }
42
+ return wrapResponse(response)
43
+ }
44
+
45
+ async function handleRestartApp(args: ToolCallArgs) {
46
+ const { platform, appId, deviceId } = args as any
47
+ const uiFingerprintBefore = await captureActionFingerprint(platform, deviceId)
48
+ ToolsNetwork.notifyActionStart()
49
+ const res = await (platform === 'android' ? new AndroidManage().restartApp(appId, deviceId) : new iOSManage().restartApp(appId, deviceId))
50
+ const uiFingerprintAfter = await captureActionFingerprint(platform, deviceId)
51
+ return wrapResponse(buildActionExecutionResult({
52
+ actionType: 'restart_app',
53
+ selector: { appId },
54
+ success: !!res.appRestarted,
55
+ uiFingerprintBefore,
56
+ uiFingerprintAfter,
57
+ failure: res.appRestarted ? undefined : inferGenericFailure((res as any).error)
58
+ }))
59
+ }
60
+
61
+ async function handleResetAppData(args: ToolCallArgs) {
62
+ const { platform, appId, deviceId } = args as any
63
+ const res = await (platform === 'android' ? new AndroidManage().resetAppData(appId, deviceId) : new iOSManage().resetAppData(appId, deviceId))
64
+ const response: ResetAppDataResponse = { device: res.device, dataCleared: res.dataCleared }
65
+ return wrapResponse(response)
66
+ }
67
+
68
+ async function handleInstallApp(args: ToolCallArgs) {
69
+ const { platform, projectType, appPath, deviceId } = args as any
70
+ const res = await ToolsManage.installAppHandler({ platform, appPath, deviceId, projectType })
71
+ const response: InstallAppResponse = {
72
+ device: res.device,
73
+ installed: res.installed,
74
+ output: (res as any).output,
75
+ error: (res as any).error
76
+ }
77
+ return wrapResponse(response)
78
+ }
79
+
80
+ async function handleBuildApp(args: ToolCallArgs) {
81
+ const { platform, projectType, projectPath, variant } = args as any
82
+ const res = await ToolsManage.buildAppHandler({ platform, projectPath, variant, projectType })
83
+ return wrapResponse(res)
84
+ }
85
+
86
+ async function handleBuildAndInstall(args: ToolCallArgs) {
87
+ const { platform, projectType, projectPath, deviceId, timeout } = args as any
88
+ const res = await ToolsManage.buildAndInstallHandler({ platform, projectPath, deviceId, timeout, projectType })
89
+ return {
90
+ content: [
91
+ { type: 'text' as const, text: res.ndjson },
92
+ { type: 'text' as const, text: JSON.stringify(res.result, null, 2) }
93
+ ]
94
+ }
95
+ }
96
+
97
+ async function handleGetLogs(args: ToolCallArgs) {
98
+ const { platform, appId, deviceId, pid, tag, level, contains, since_seconds, limit, lines } = args as any
99
+ const res = await ToolsObserve.getLogsHandler({ platform, appId, deviceId, pid, tag, level, contains, since_seconds, limit, lines })
100
+ const filtered = !!(pid || tag || level || contains || since_seconds || appId)
101
+ return {
102
+ content: [
103
+ { type: 'text' as const, text: JSON.stringify({ device: res.device, result: { count: res.logCount, filtered, crashLines: (res.crashLines || []), source: res.source, meta: res.meta || {} } }, null, 2) },
104
+ { type: 'text' as const, text: JSON.stringify({ logs: res.logs }, null, 2) }
105
+ ]
106
+ }
107
+ }
108
+
109
+ async function handleListDevices(args: ToolCallArgs) {
110
+ const { platform, appId } = args as any
111
+ const res = await ToolsManage.listDevicesHandler({ platform, appId })
112
+ return wrapResponse(res)
113
+ }
114
+
115
+ async function handleGetSystemStatus() {
116
+ const result = await getSystemStatus()
117
+ return wrapResponse(result)
118
+ }
119
+
120
+ async function handleCaptureScreenshot(args: ToolCallArgs) {
121
+ const { platform, deviceId } = args as any
122
+ const res = await ToolsObserve.captureScreenshotHandler({ platform, deviceId })
123
+ const mime = (res as any).screenshot_mime || 'image/png'
124
+ const content: Array<{ type: 'text' | 'image'; text?: string; data?: string; mimeType?: string }> = [
125
+ { type: 'text', text: JSON.stringify({ device: res.device, result: { resolution: (res as any).resolution, mimeType: mime } }, null, 2) },
126
+ { type: 'image', data: (res as any).screenshot, mimeType: mime }
127
+ ]
128
+ if ((res as any).screenshot_fallback) {
129
+ content.push({ type: 'text', text: JSON.stringify({ note: 'JPEG fallback included for compatibility', mimeType: (res as any).screenshot_fallback_mime || 'image/jpeg' }) })
130
+ content.push({ type: 'image', data: (res as any).screenshot_fallback, mimeType: (res as any).screenshot_fallback_mime || 'image/jpeg' })
131
+ }
132
+ return { content }
133
+ }
134
+
135
+ async function handleCaptureDebugSnapshot(args: ToolCallArgs) {
136
+ const { reason, includeLogs, logLines, platform, appId, deviceId, sessionId } = args as any
137
+ const res = await ToolsObserve.captureDebugSnapshotHandler({ reason, includeLogs, logLines, platform, appId, deviceId, sessionId })
138
+ return wrapResponse(res)
139
+ }
140
+
141
+ async function handleGetUITree(args: ToolCallArgs) {
142
+ const { platform, deviceId } = args as any
143
+ const res = await ToolsObserve.getUITreeHandler({ platform, deviceId })
144
+ return wrapResponse(res)
145
+ }
146
+
147
+ async function handleGetCurrentScreen(args: ToolCallArgs) {
148
+ const { deviceId } = args as any
149
+ const res = await ToolsObserve.getCurrentScreenHandler({ deviceId })
150
+ return wrapResponse(res)
151
+ }
152
+
153
+ async function handleGetScreenFingerprint(args: ToolCallArgs) {
154
+ const { platform, deviceId } = args as any
155
+ const res = await ToolsObserve.getScreenFingerprintHandler({ platform, deviceId })
156
+ return wrapResponse(res)
157
+ }
158
+
159
+ async function handleWaitForScreenChange(args: ToolCallArgs) {
160
+ const { platform, previousFingerprint, timeoutMs, pollIntervalMs, deviceId } = args as any
161
+ const res = await ToolsInteract.waitForScreenChangeHandler({ platform, previousFingerprint, timeoutMs, pollIntervalMs, deviceId })
162
+ return wrapResponse(res)
163
+ }
164
+
165
+ async function handleExpectScreen(args: ToolCallArgs) {
166
+ const { platform, fingerprint, screen, deviceId } = args as any
167
+ const res = await ToolsInteract.expectScreenHandler({ platform, fingerprint, screen, deviceId })
168
+ return wrapResponse(res)
169
+ }
170
+
171
+ async function handleExpectElementVisible(args: ToolCallArgs) {
172
+ const { selector, element_id, timeout_ms, poll_interval_ms, platform, deviceId } = args as any
173
+ const res = await ToolsInteract.expectElementVisibleHandler({ selector, element_id, timeout_ms, poll_interval_ms, platform, deviceId })
174
+ return wrapResponse(res)
175
+ }
176
+
177
+ async function handleWaitForUI(args: ToolCallArgs) {
178
+ const { selector, condition = 'exists', timeout_ms = 60000, poll_interval_ms = 300, match, retry, platform, deviceId } = args as any
179
+ const res = await ToolsInteract.waitForUIHandler({ selector, condition, timeout_ms, poll_interval_ms, match, retry, platform, deviceId })
180
+ return wrapResponse(res)
181
+ }
182
+
183
+ async function handleFindElement(args: ToolCallArgs) {
184
+ const { query, exact = false, timeoutMs = 3000, platform, deviceId } = args as any
185
+ const res = await ToolsInteract.findElementHandler({ query, exact, timeoutMs, platform, deviceId })
186
+ return wrapResponse(res)
187
+ }
188
+
189
+ async function handleTap(args: ToolCallArgs) {
190
+ const { platform, x, y, deviceId } = args as any
191
+ const uiFingerprintBefore = await captureActionFingerprint(platform, deviceId)
192
+ ToolsNetwork.notifyActionStart()
193
+ const res = await ToolsInteract.tapHandler({ platform, x, y, deviceId })
194
+ const uiFingerprintAfter = await captureActionFingerprint(platform, deviceId)
195
+ return wrapResponse(buildActionExecutionResult({
196
+ actionType: 'tap',
197
+ selector: { x, y },
198
+ success: !!res.success,
199
+ uiFingerprintBefore,
200
+ uiFingerprintAfter,
201
+ failure: res.success ? undefined : inferGenericFailure((res as any).error)
202
+ }))
203
+ }
204
+
205
+ async function handleTapElement(args: ToolCallArgs) {
206
+ const { elementId } = args as any
207
+ ToolsNetwork.notifyActionStart()
208
+ const res = await ToolsInteract.tapElementHandler({ elementId })
209
+ return wrapResponse(res)
210
+ }
211
+
212
+ async function handleSwipe(args: ToolCallArgs) {
213
+ const { platform = 'android', x1, y1, x2, y2, duration, deviceId } = args as any
214
+ const uiFingerprintBefore = await captureActionFingerprint(platform, deviceId)
215
+ ToolsNetwork.notifyActionStart()
216
+ const res = await ToolsInteract.swipeHandler({ platform, x1, y1, x2, y2, duration, deviceId })
217
+ const uiFingerprintAfter = await captureActionFingerprint(platform, deviceId)
218
+ return wrapResponse(buildActionExecutionResult({
219
+ actionType: 'swipe',
220
+ selector: { x1, y1, x2, y2, duration },
221
+ success: !!res.success,
222
+ uiFingerprintBefore,
223
+ uiFingerprintAfter,
224
+ failure: res.success ? undefined : inferGenericFailure((res as any).error)
225
+ }))
226
+ }
227
+
228
+ async function handleScrollToElement(args: ToolCallArgs) {
229
+ const { platform, selector, direction, maxScrolls, scrollAmount, deviceId } = args as any
230
+ const uiFingerprintBefore = await captureActionFingerprint(platform, deviceId)
231
+ ToolsNetwork.notifyActionStart()
232
+ const res = await ToolsInteract.scrollToElementHandler({ platform, selector, direction, maxScrolls, scrollAmount, deviceId })
233
+ const uiFingerprintAfter = await captureActionFingerprint(platform, deviceId)
234
+ return wrapResponse(buildActionExecutionResult({
235
+ actionType: 'scroll_to_element',
236
+ selector,
237
+ resolved: res?.success && res?.element ? {
238
+ elementId: null,
239
+ text: (res.element as any).text ?? null,
240
+ resource_id: (res.element as any).resourceId ?? null,
241
+ accessibility_id: (res.element as any).contentDesc ?? null,
242
+ class: (res.element as any).className ?? null,
243
+ bounds: (res.element as any).bounds ?? null,
244
+ index: null
245
+ } : null,
246
+ success: !!res.success,
247
+ uiFingerprintBefore,
248
+ uiFingerprintAfter,
249
+ failure: res.success ? undefined : inferScrollFailure((res as any).reason)
250
+ }))
251
+ }
252
+
253
+ async function handleTypeText(args: ToolCallArgs) {
254
+ const { text, deviceId } = args as any
255
+ const uiFingerprintBefore = await captureActionFingerprint('android', deviceId)
256
+ ToolsNetwork.notifyActionStart()
257
+ const res = await ToolsInteract.typeTextHandler({ text, deviceId })
258
+ const uiFingerprintAfter = await captureActionFingerprint('android', deviceId)
259
+ return wrapResponse(buildActionExecutionResult({
260
+ actionType: 'type_text',
261
+ selector: { text },
262
+ success: !!res.success,
263
+ uiFingerprintBefore,
264
+ uiFingerprintAfter,
265
+ failure: res.success ? undefined : inferGenericFailure((res as any).error)
266
+ }))
267
+ }
268
+
269
+ async function handlePressBack(args: ToolCallArgs) {
270
+ const { deviceId } = args as any
271
+ const uiFingerprintBefore = await captureActionFingerprint('android', deviceId)
272
+ ToolsNetwork.notifyActionStart()
273
+ const res = await ToolsInteract.pressBackHandler({ deviceId })
274
+ const uiFingerprintAfter = await captureActionFingerprint('android', deviceId)
275
+ return wrapResponse(buildActionExecutionResult({
276
+ actionType: 'press_back',
277
+ selector: { key: 'back' },
278
+ success: !!res.success,
279
+ uiFingerprintBefore,
280
+ uiFingerprintAfter,
281
+ failure: res.success ? undefined : inferGenericFailure((res as any).error)
282
+ }))
283
+ }
284
+
285
+ async function handleStartLogStream(args: ToolCallArgs) {
286
+ const { platform, packageName, level, sessionId, deviceId } = args as any
287
+ const res = await ToolsObserve.startLogStreamHandler({ platform, packageName, level, sessionId, deviceId })
288
+ return wrapResponse(res)
289
+ }
290
+
291
+ async function handleReadLogStream(args: ToolCallArgs) {
292
+ const { platform, sessionId, limit, since } = args as any
293
+ const res = await ToolsObserve.readLogStreamHandler({ platform, sessionId, limit, since })
294
+ return wrapResponse(res)
295
+ }
296
+
297
+ async function handleStopLogStream(args: ToolCallArgs) {
298
+ const { platform, sessionId } = args as any
299
+ const res = await ToolsObserve.stopLogStreamHandler({ platform, sessionId })
300
+ return wrapResponse(res)
301
+ }
302
+
303
+ function handleClassifyActionOutcome(args: ToolCallArgs) {
304
+ const { uiChanged, expectedElementVisible, networkRequests, hasLogErrors } = args as any
305
+ const result = classifyActionOutcome({
306
+ uiChanged: Boolean(uiChanged),
307
+ expectedElementVisible: expectedElementVisible ?? null,
308
+ networkRequests: networkRequests ?? null,
309
+ hasLogErrors: hasLogErrors ?? null
310
+ })
311
+ return Promise.resolve(wrapResponse(result))
312
+ }
313
+
314
+ async function handleGetNetworkActivity(args: ToolCallArgs) {
315
+ const { platform, deviceId } = args as any
316
+ const result = await ToolsNetwork.getNetworkActivity({ platform, deviceId })
317
+ return wrapResponse(result)
318
+ }
319
+
320
+ export const toolHandlers: Record<string, ToolHandler> = {
321
+ start_app: handleStartApp,
322
+ terminate_app: handleTerminateApp,
323
+ restart_app: handleRestartApp,
324
+ reset_app_data: handleResetAppData,
325
+ install_app: handleInstallApp,
326
+ build_app: handleBuildApp,
327
+ build_and_install: handleBuildAndInstall,
328
+ get_logs: handleGetLogs,
329
+ list_devices: handleListDevices,
330
+ get_system_status: handleGetSystemStatus,
331
+ capture_screenshot: handleCaptureScreenshot,
332
+ capture_debug_snapshot: handleCaptureDebugSnapshot,
333
+ get_ui_tree: handleGetUITree,
334
+ get_current_screen: handleGetCurrentScreen,
335
+ get_screen_fingerprint: handleGetScreenFingerprint,
336
+ wait_for_screen_change: handleWaitForScreenChange,
337
+ expect_screen: handleExpectScreen,
338
+ expect_element_visible: handleExpectElementVisible,
339
+ wait_for_ui: handleWaitForUI,
340
+ find_element: handleFindElement,
341
+ tap: handleTap,
342
+ tap_element: handleTapElement,
343
+ swipe: handleSwipe,
344
+ scroll_to_element: handleScrollToElement,
345
+ type_text: handleTypeText,
346
+ press_back: handlePressBack,
347
+ start_log_stream: handleStartLogStream,
348
+ read_log_stream: handleReadLogStream,
349
+ stop_log_stream: handleStopLogStream,
350
+ classify_action_outcome: handleClassifyActionOutcome,
351
+ get_network_activity: handleGetNetworkActivity
352
+ }
353
+
354
+ export async function handleToolCall(name: string, args: ToolCallArgs = {}) {
355
+ const handler = toolHandlers[name]
356
+ if (!handler) throw new Error(`Unknown tool: ${name}`)
357
+
358
+ try {
359
+ return await handler(args)
360
+ } catch (error) {
361
+ return {
362
+ content: [{ type: 'text' as const, text: `Error executing tool ${name}: ${error instanceof Error ? error.message : String(error)}` }]
363
+ }
364
+ }
365
+ }