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.
- package/dist/interact/index.js +133 -57
- package/dist/manage/android.js +27 -4
- package/dist/server/common.js +66 -0
- package/dist/server/tool-definitions.js +921 -0
- package/dist/server/tool-handlers.js +320 -0
- package/dist/server-core.js +4 -801
- package/docs/CHANGELOG.md +6 -0
- package/docs/tools/TOOLS.md +15 -7
- package/docs/tools/interact.md +270 -107
- package/docs/tools/manage.md +39 -38
- package/docs/tools/observe.md +30 -8
- package/docs/tools/system.md +1 -1
- package/package.json +1 -1
- package/src/interact/index.ts +186 -58
- package/src/manage/android.ts +27 -3
- package/src/server/common.ts +95 -0
- package/src/server/tool-definitions.ts +921 -0
- package/src/server/tool-handlers.ts +365 -0
- package/src/server-core.ts +4 -844
- package/src/types.ts +59 -6
- package/test/unit/interact/expect_tools.test.ts +77 -0
- package/test/unit/interact/tap_element.test.ts +23 -6
- package/test/unit/manage/install.test.ts +94 -1
- package/test/unit/server/contract.test.ts +26 -0
- package/test/unit/server/response_shapes.test.ts +69 -4
package/dist/server-core.js
CHANGED
|
@@ -1,810 +1,13 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
2
|
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
import { ToolsNetwork } from './network/index.js';
|
|
8
|
-
import { AndroidManage } from './manage/index.js';
|
|
9
|
-
import { iOSManage } from './manage/index.js';
|
|
10
|
-
import { getSystemStatus } from './system/index.js';
|
|
3
|
+
import { wrapResponse } from './server/common.js';
|
|
4
|
+
import { toolDefinitions } from './server/tool-definitions.js';
|
|
5
|
+
import { handleToolCall } from './server/tool-handlers.js';
|
|
6
|
+
export { wrapResponse, toolDefinitions, handleToolCall };
|
|
11
7
|
export const serverInfo = {
|
|
12
8
|
name: 'mobile-debug-mcp',
|
|
13
9
|
version: '0.7.0'
|
|
14
10
|
};
|
|
15
|
-
export function wrapResponse(data) {
|
|
16
|
-
return {
|
|
17
|
-
content: [{
|
|
18
|
-
type: 'text',
|
|
19
|
-
text: JSON.stringify(data, null, 2)
|
|
20
|
-
}]
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
export const toolDefinitions = [
|
|
24
|
-
{
|
|
25
|
-
name: 'start_app',
|
|
26
|
-
description: 'Launch a mobile app on Android or iOS simulator',
|
|
27
|
-
inputSchema: {
|
|
28
|
-
type: 'object',
|
|
29
|
-
properties: {
|
|
30
|
-
platform: {
|
|
31
|
-
type: 'string',
|
|
32
|
-
enum: ['android', 'ios']
|
|
33
|
-
},
|
|
34
|
-
appId: {
|
|
35
|
-
type: 'string',
|
|
36
|
-
description: 'Android package name or iOS bundle id'
|
|
37
|
-
},
|
|
38
|
-
deviceId: {
|
|
39
|
-
type: 'string',
|
|
40
|
-
description: 'Device UDID (iOS) or Serial (Android). Defaults to booted/connected.'
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
required: ['platform', 'appId']
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
name: 'terminate_app',
|
|
48
|
-
description: 'Terminate a mobile app on Android or iOS simulator',
|
|
49
|
-
inputSchema: {
|
|
50
|
-
type: 'object',
|
|
51
|
-
properties: {
|
|
52
|
-
platform: {
|
|
53
|
-
type: 'string',
|
|
54
|
-
enum: ['android', 'ios']
|
|
55
|
-
},
|
|
56
|
-
appId: {
|
|
57
|
-
type: 'string',
|
|
58
|
-
description: 'Android package name or iOS bundle id'
|
|
59
|
-
},
|
|
60
|
-
deviceId: {
|
|
61
|
-
type: 'string',
|
|
62
|
-
description: 'Device UDID (iOS) or Serial (Android). Defaults to booted/connected.'
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
required: ['platform', 'appId']
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
name: 'restart_app',
|
|
70
|
-
description: 'Restart a mobile app on Android or iOS simulator',
|
|
71
|
-
inputSchema: {
|
|
72
|
-
type: 'object',
|
|
73
|
-
properties: {
|
|
74
|
-
platform: {
|
|
75
|
-
type: 'string',
|
|
76
|
-
enum: ['android', 'ios']
|
|
77
|
-
},
|
|
78
|
-
appId: {
|
|
79
|
-
type: 'string',
|
|
80
|
-
description: 'Android package name or iOS bundle id'
|
|
81
|
-
},
|
|
82
|
-
deviceId: {
|
|
83
|
-
type: 'string',
|
|
84
|
-
description: 'Device UDID (iOS) or Serial (Android). Defaults to booted/connected.'
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
required: ['platform', 'appId']
|
|
88
|
-
}
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
name: 'reset_app_data',
|
|
92
|
-
description: 'Reset app data (clear storage) for a mobile app on Android or iOS simulator',
|
|
93
|
-
inputSchema: {
|
|
94
|
-
type: 'object',
|
|
95
|
-
properties: {
|
|
96
|
-
platform: {
|
|
97
|
-
type: 'string',
|
|
98
|
-
enum: ['android', 'ios']
|
|
99
|
-
},
|
|
100
|
-
appId: {
|
|
101
|
-
type: 'string',
|
|
102
|
-
description: 'Android package name or iOS bundle id'
|
|
103
|
-
},
|
|
104
|
-
deviceId: {
|
|
105
|
-
type: 'string',
|
|
106
|
-
description: 'Device UDID (iOS) or Serial (Android). Defaults to booted/connected.'
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
required: ['platform', 'appId']
|
|
110
|
-
}
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
name: 'install_app',
|
|
114
|
-
description: 'Install an app on Android or iOS. Accepts a built binary (apk/.ipa/.app) or a project directory to build then install. platform and projectType are required.',
|
|
115
|
-
inputSchema: {
|
|
116
|
-
type: 'object',
|
|
117
|
-
properties: {
|
|
118
|
-
platform: { type: 'string', enum: ['android', 'ios'], description: 'Platform to install to (required).' },
|
|
119
|
-
projectType: { type: 'string', enum: ['native', 'kmp', 'react-native', 'flutter'], description: 'Project type to guide build/install tool selection (required).' },
|
|
120
|
-
appPath: { type: 'string', description: 'Path to APK, .app, .ipa, or project directory' },
|
|
121
|
-
deviceId: { type: 'string', description: 'Device UDID (iOS) or Serial (Android). Defaults to booted/connected.' }
|
|
122
|
-
},
|
|
123
|
-
required: ['platform', 'projectType', 'appPath']
|
|
124
|
-
}
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
name: 'build_app',
|
|
128
|
-
description: 'Build a project for Android or iOS and return the built artifact path. Does not install. platform and projectType are required.',
|
|
129
|
-
inputSchema: {
|
|
130
|
-
type: 'object',
|
|
131
|
-
properties: {
|
|
132
|
-
platform: { type: 'string', enum: ['android', 'ios'], description: 'Platform to build for (required).' },
|
|
133
|
-
projectType: { type: 'string', enum: ['native', 'kmp', 'react-native', 'flutter'], description: 'Project type to guide build tool selection (required).' },
|
|
134
|
-
projectPath: { type: 'string', description: 'Path to project directory (contains gradlew or xcodeproj/xcworkspace)' },
|
|
135
|
-
variant: { type: 'string', description: 'Optional build variant (e.g., Debug/Release)' }
|
|
136
|
-
},
|
|
137
|
-
required: ['platform', 'projectType', 'projectPath']
|
|
138
|
-
}
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
name: 'get_logs',
|
|
142
|
-
description: 'Get recent logs from Android or iOS simulator. Returns device metadata and structured logs suitable for AI consumption.',
|
|
143
|
-
inputSchema: {
|
|
144
|
-
type: 'object',
|
|
145
|
-
properties: {
|
|
146
|
-
platform: {
|
|
147
|
-
type: 'string',
|
|
148
|
-
enum: ['android', 'ios']
|
|
149
|
-
},
|
|
150
|
-
appId: {
|
|
151
|
-
type: 'string',
|
|
152
|
-
description: 'Filter by Android package name or iOS bundle id'
|
|
153
|
-
},
|
|
154
|
-
deviceId: {
|
|
155
|
-
type: 'string',
|
|
156
|
-
description: 'Device UDID (iOS) or Serial (Android). Defaults to booted/connected.'
|
|
157
|
-
},
|
|
158
|
-
pid: { type: 'number', description: 'Filter by process id' },
|
|
159
|
-
tag: { type: 'string', description: 'Filter by tag (Android) or subsystem/category (iOS)' },
|
|
160
|
-
level: { type: 'string', description: 'Log level filter (VERBOSE, DEBUG, INFO, WARN, ERROR)' },
|
|
161
|
-
contains: { type: 'string', description: 'Substring to match in log message' },
|
|
162
|
-
since_seconds: { type: 'number', description: 'Only return logs from the last N seconds' },
|
|
163
|
-
limit: { type: 'number', description: 'Override default number of returned lines' },
|
|
164
|
-
lines: {
|
|
165
|
-
type: 'number',
|
|
166
|
-
description: 'Legacy - number of log lines (android only)'
|
|
167
|
-
}
|
|
168
|
-
},
|
|
169
|
-
required: ['platform']
|
|
170
|
-
}
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
name: 'list_devices',
|
|
174
|
-
description: 'List connected devices and their metadata (android + ios).',
|
|
175
|
-
inputSchema: {
|
|
176
|
-
type: 'object',
|
|
177
|
-
properties: {
|
|
178
|
-
platform: { type: 'string', enum: ['android', 'ios'] }
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
},
|
|
182
|
-
{
|
|
183
|
-
name: 'get_system_status',
|
|
184
|
-
description: 'Quick healthcheck of local mobile debugging environment (adb, devices, logs, env, iOS).',
|
|
185
|
-
inputSchema: { type: 'object', properties: {} }
|
|
186
|
-
},
|
|
187
|
-
{
|
|
188
|
-
name: 'capture_screenshot',
|
|
189
|
-
description: 'Capture a screenshot from an Android device or iOS simulator. Returns device metadata and the screenshot image.',
|
|
190
|
-
inputSchema: {
|
|
191
|
-
type: 'object',
|
|
192
|
-
properties: {
|
|
193
|
-
platform: {
|
|
194
|
-
type: 'string',
|
|
195
|
-
enum: ['android', 'ios']
|
|
196
|
-
},
|
|
197
|
-
deviceId: {
|
|
198
|
-
type: 'string',
|
|
199
|
-
description: 'Device UDID (iOS) or Serial (Android). Defaults to booted/connected.'
|
|
200
|
-
}
|
|
201
|
-
},
|
|
202
|
-
required: ['platform']
|
|
203
|
-
}
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
name: 'capture_debug_snapshot',
|
|
207
|
-
description: 'Capture a complete debug snapshot (screenshot, ui tree, activity, fingerprint, logs). Returns structured JSON.',
|
|
208
|
-
inputSchema: {
|
|
209
|
-
type: 'object',
|
|
210
|
-
properties: {
|
|
211
|
-
reason: { type: 'string', description: 'Optional reason for snapshot' },
|
|
212
|
-
includeLogs: { type: 'boolean', description: 'Whether to include logs', default: true },
|
|
213
|
-
logLines: { type: 'number', description: 'Maximum number of log lines to include', default: 200 },
|
|
214
|
-
platform: { type: 'string', enum: ['android', 'ios'], description: 'Optional platform override' },
|
|
215
|
-
appId: { type: 'string', description: 'Optional appId to scope logs (package/bundle id)' },
|
|
216
|
-
deviceId: { type: 'string', description: 'Optional device serial/udid' },
|
|
217
|
-
sessionId: { type: 'string', description: 'Optional log stream session id to prefer' }
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
},
|
|
221
|
-
{
|
|
222
|
-
name: 'start_log_stream',
|
|
223
|
-
description: 'Start streaming logs for a target application on Android or iOS. For Android this uses adb logcat --pid=<pid>; for iOS it streams `xcrun simctl spawn <device> log stream` with a predicate.',
|
|
224
|
-
inputSchema: {
|
|
225
|
-
type: 'object',
|
|
226
|
-
properties: {
|
|
227
|
-
platform: { type: 'string', enum: ['android', 'ios'], default: 'android' },
|
|
228
|
-
packageName: { type: 'string', description: 'Android package name or iOS bundle id' },
|
|
229
|
-
level: { type: 'string', enum: ['error', 'warn', 'info', 'debug'], default: 'error' },
|
|
230
|
-
deviceId: { type: 'string', description: 'Device Serial (Android) or UDID (iOS). Defaults to connected/booted device.' },
|
|
231
|
-
sessionId: { type: 'string', description: 'Session identifier for the log stream' }
|
|
232
|
-
},
|
|
233
|
-
required: ['packageName']
|
|
234
|
-
}
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
name: 'read_log_stream',
|
|
238
|
-
description: 'Read accumulated log stream entries for the active session.',
|
|
239
|
-
inputSchema: {
|
|
240
|
-
type: 'object',
|
|
241
|
-
properties: {
|
|
242
|
-
sessionId: { type: 'string' }
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
},
|
|
246
|
-
{
|
|
247
|
-
name: 'stop_log_stream',
|
|
248
|
-
description: 'Stop an active log stream for the session.',
|
|
249
|
-
inputSchema: {
|
|
250
|
-
type: 'object',
|
|
251
|
-
properties: {
|
|
252
|
-
sessionId: { type: 'string' }
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
},
|
|
256
|
-
{
|
|
257
|
-
name: 'get_ui_tree',
|
|
258
|
-
description: 'Get the current UI hierarchy from an Android device or iOS simulator. Returns a structured JSON representation of the screen content.',
|
|
259
|
-
inputSchema: {
|
|
260
|
-
type: 'object',
|
|
261
|
-
properties: {
|
|
262
|
-
platform: {
|
|
263
|
-
type: 'string',
|
|
264
|
-
enum: ['android', 'ios'],
|
|
265
|
-
description: 'Platform to get UI tree for'
|
|
266
|
-
},
|
|
267
|
-
deviceId: {
|
|
268
|
-
type: 'string',
|
|
269
|
-
description: 'Device Serial (Android) or UDID (iOS). Defaults to connected/booted device.'
|
|
270
|
-
}
|
|
271
|
-
},
|
|
272
|
-
required: ['platform']
|
|
273
|
-
}
|
|
274
|
-
},
|
|
275
|
-
{
|
|
276
|
-
name: 'get_current_screen',
|
|
277
|
-
description: 'Get the currently visible activity on an Android device. Returns package and activity name.',
|
|
278
|
-
inputSchema: {
|
|
279
|
-
type: 'object',
|
|
280
|
-
properties: {
|
|
281
|
-
deviceId: {
|
|
282
|
-
type: 'string',
|
|
283
|
-
description: 'Device Serial (Android). Defaults to connected/booted device.'
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
},
|
|
288
|
-
{
|
|
289
|
-
name: 'get_screen_fingerprint',
|
|
290
|
-
description: 'Generate a stable fingerprint representing the current visible screen (activity + visible UI elements).',
|
|
291
|
-
inputSchema: {
|
|
292
|
-
type: 'object',
|
|
293
|
-
properties: {
|
|
294
|
-
platform: { type: 'string', enum: ['android', 'ios'], description: 'Optional platform override (android|ios)' },
|
|
295
|
-
deviceId: { type: 'string', description: 'Optional device id/udid to target' }
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
},
|
|
299
|
-
{
|
|
300
|
-
name: 'wait_for_screen_change',
|
|
301
|
-
description: 'Wait until the current screen fingerprint differs from a provided previousFingerprint. Useful to wait for navigation/animation completion.',
|
|
302
|
-
inputSchema: {
|
|
303
|
-
type: 'object',
|
|
304
|
-
properties: {
|
|
305
|
-
platform: { type: 'string', enum: ['android', 'ios'], description: 'Optional platform override (android|ios)' },
|
|
306
|
-
previousFingerprint: { type: 'string', description: 'The fingerprint to compare against (required)' },
|
|
307
|
-
timeoutMs: { type: 'number', description: 'Timeout in ms to wait for change (default 5000)', default: 5000 },
|
|
308
|
-
pollIntervalMs: { type: 'number', description: 'Polling interval in ms (default 300)', default: 300 },
|
|
309
|
-
deviceId: { type: 'string', description: 'Optional device id/udid to target' }
|
|
310
|
-
},
|
|
311
|
-
required: ['previousFingerprint']
|
|
312
|
-
}
|
|
313
|
-
},
|
|
314
|
-
{
|
|
315
|
-
name: 'wait_for_ui',
|
|
316
|
-
description: 'Deterministic UI wait primitive. Waits for selector condition with retries and backoff.',
|
|
317
|
-
inputSchema: {
|
|
318
|
-
type: 'object',
|
|
319
|
-
properties: {
|
|
320
|
-
selector: {
|
|
321
|
-
type: 'object',
|
|
322
|
-
properties: {
|
|
323
|
-
text: { type: 'string' },
|
|
324
|
-
resource_id: { type: 'string' },
|
|
325
|
-
accessibility_id: { type: 'string' },
|
|
326
|
-
contains: { type: 'boolean', description: 'When true, perform substring matching', default: false }
|
|
327
|
-
}
|
|
328
|
-
},
|
|
329
|
-
condition: { type: 'string', enum: ['exists', 'not_exists', 'visible', 'clickable'], default: 'exists' },
|
|
330
|
-
timeout_ms: { type: 'number', default: 60000 },
|
|
331
|
-
poll_interval_ms: { type: 'number', default: 300 },
|
|
332
|
-
match: { type: 'object', properties: { index: { type: 'number' } } },
|
|
333
|
-
retry: { type: 'object', properties: { max_attempts: { type: 'number', default: 1 }, backoff_ms: { type: 'number', default: 0 } } },
|
|
334
|
-
platform: { type: 'string', enum: ['android', 'ios'], description: 'Optional platform override' },
|
|
335
|
-
deviceId: { type: 'string', description: 'Optional device serial/udid' }
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
},
|
|
339
|
-
{
|
|
340
|
-
name: 'find_element',
|
|
341
|
-
description: 'Find a UI element by semantic query (text, content-desc, resource-id, class). Returns best match.',
|
|
342
|
-
inputSchema: {
|
|
343
|
-
type: 'object',
|
|
344
|
-
properties: {
|
|
345
|
-
query: { type: 'string', description: 'Search query (text or label)' },
|
|
346
|
-
exact: { type: 'boolean', description: 'Require exact match (true/false)', default: false },
|
|
347
|
-
timeoutMs: { type: 'number', description: 'Timeout in ms to keep searching', default: 3000 },
|
|
348
|
-
platform: { type: 'string', enum: ['android', 'ios'], description: 'Optional platform override' },
|
|
349
|
-
deviceId: { type: 'string', description: 'Optional device serial/udid' }
|
|
350
|
-
},
|
|
351
|
-
required: ['query']
|
|
352
|
-
}
|
|
353
|
-
},
|
|
354
|
-
{
|
|
355
|
-
name: 'tap',
|
|
356
|
-
description: 'Simulate a finger tap on the device screen at specific coordinates.',
|
|
357
|
-
inputSchema: {
|
|
358
|
-
type: 'object',
|
|
359
|
-
properties: {
|
|
360
|
-
platform: {
|
|
361
|
-
type: 'string',
|
|
362
|
-
enum: ['android', 'ios'],
|
|
363
|
-
description: 'Platform to tap on'
|
|
364
|
-
},
|
|
365
|
-
x: {
|
|
366
|
-
type: 'number',
|
|
367
|
-
description: 'X coordinate'
|
|
368
|
-
},
|
|
369
|
-
y: {
|
|
370
|
-
type: 'number',
|
|
371
|
-
description: 'Y coordinate'
|
|
372
|
-
},
|
|
373
|
-
deviceId: {
|
|
374
|
-
type: 'string',
|
|
375
|
-
description: 'Device Serial/UDID. Defaults to connected/booted device.'
|
|
376
|
-
}
|
|
377
|
-
},
|
|
378
|
-
required: ['x', 'y']
|
|
379
|
-
}
|
|
380
|
-
},
|
|
381
|
-
{
|
|
382
|
-
name: 'tap_element',
|
|
383
|
-
description: 'Tap a previously resolved UI element using its elementId.',
|
|
384
|
-
inputSchema: {
|
|
385
|
-
type: 'object',
|
|
386
|
-
properties: {
|
|
387
|
-
elementId: {
|
|
388
|
-
type: 'string',
|
|
389
|
-
description: 'A unique element identifier returned by wait_for_ui'
|
|
390
|
-
}
|
|
391
|
-
},
|
|
392
|
-
required: ['elementId']
|
|
393
|
-
}
|
|
394
|
-
},
|
|
395
|
-
{
|
|
396
|
-
name: 'swipe',
|
|
397
|
-
description: 'Simulate a swipe gesture on an Android device.',
|
|
398
|
-
inputSchema: {
|
|
399
|
-
type: 'object',
|
|
400
|
-
properties: {
|
|
401
|
-
platform: {
|
|
402
|
-
type: 'string',
|
|
403
|
-
enum: ['android', 'ios'],
|
|
404
|
-
description: 'Platform to swipe on (android or ios)'
|
|
405
|
-
},
|
|
406
|
-
x1: { type: 'number', description: 'Start X coordinate' },
|
|
407
|
-
y1: { type: 'number', description: 'Start Y coordinate' },
|
|
408
|
-
x2: { type: 'number', description: 'End X coordinate' },
|
|
409
|
-
y2: { type: 'number', description: 'End Y coordinate' },
|
|
410
|
-
duration: { type: 'number', description: 'Duration in ms' },
|
|
411
|
-
deviceId: {
|
|
412
|
-
type: 'string',
|
|
413
|
-
description: 'Device Serial/UDID. Defaults to connected/booted device.'
|
|
414
|
-
}
|
|
415
|
-
},
|
|
416
|
-
required: ['x1', 'y1', 'x2', 'y2', 'duration']
|
|
417
|
-
}
|
|
418
|
-
},
|
|
419
|
-
{
|
|
420
|
-
name: 'scroll_to_element',
|
|
421
|
-
description: 'Scroll the current screen until a target UI element becomes visible, then return its details.',
|
|
422
|
-
inputSchema: {
|
|
423
|
-
type: 'object',
|
|
424
|
-
properties: {
|
|
425
|
-
platform: { type: 'string', enum: ['android', 'ios'], description: 'Platform to operate on (required)' },
|
|
426
|
-
selector: {
|
|
427
|
-
type: 'object',
|
|
428
|
-
properties: {
|
|
429
|
-
text: { type: 'string' },
|
|
430
|
-
resourceId: { type: 'string' },
|
|
431
|
-
contentDesc: { type: 'string' },
|
|
432
|
-
className: { type: 'string' }
|
|
433
|
-
}
|
|
434
|
-
},
|
|
435
|
-
direction: { type: 'string', enum: ['down', 'up'], default: 'down' },
|
|
436
|
-
maxScrolls: { type: 'number', default: 10 },
|
|
437
|
-
scrollAmount: { type: 'number', default: 0.7 },
|
|
438
|
-
deviceId: { type: 'string', description: 'Device UDID (iOS) or Serial (Android). Defaults to booted/connected.' }
|
|
439
|
-
},
|
|
440
|
-
required: ['platform', 'selector']
|
|
441
|
-
}
|
|
442
|
-
},
|
|
443
|
-
{
|
|
444
|
-
name: 'type_text',
|
|
445
|
-
description: 'Type text into the currently focused input field on an Android device.',
|
|
446
|
-
inputSchema: {
|
|
447
|
-
type: 'object',
|
|
448
|
-
properties: {
|
|
449
|
-
platform: {
|
|
450
|
-
type: 'string',
|
|
451
|
-
enum: ['android'],
|
|
452
|
-
description: 'Platform to type on (currently only android supported)'
|
|
453
|
-
},
|
|
454
|
-
text: {
|
|
455
|
-
type: 'string',
|
|
456
|
-
description: 'The text to type'
|
|
457
|
-
},
|
|
458
|
-
deviceId: {
|
|
459
|
-
type: 'string',
|
|
460
|
-
description: 'Device Serial/UDID. Defaults to connected/booted device.'
|
|
461
|
-
}
|
|
462
|
-
},
|
|
463
|
-
required: ['text']
|
|
464
|
-
}
|
|
465
|
-
},
|
|
466
|
-
{
|
|
467
|
-
name: 'press_back',
|
|
468
|
-
description: 'Simulate pressing the Android Back button.',
|
|
469
|
-
inputSchema: {
|
|
470
|
-
type: 'object',
|
|
471
|
-
properties: {
|
|
472
|
-
platform: {
|
|
473
|
-
type: 'string',
|
|
474
|
-
enum: ['android'],
|
|
475
|
-
description: 'Platform (currently only android supported)'
|
|
476
|
-
},
|
|
477
|
-
deviceId: {
|
|
478
|
-
type: 'string',
|
|
479
|
-
description: 'Device Serial/UDID. Defaults to connected/booted device.'
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
},
|
|
484
|
-
{
|
|
485
|
-
name: 'classify_action_outcome',
|
|
486
|
-
description: `Classify the outcome of the most recent action into exactly one of: success, no_op, backend_failure, ui_failure, unknown.
|
|
487
|
-
|
|
488
|
-
MUST be called after every action (tap, swipe, type_text, press_back, start_app, etc). Never skip.
|
|
489
|
-
|
|
490
|
-
HOW TO GATHER INPUTS before calling:
|
|
491
|
-
1. Call wait_for_screen_change or compare get_screen_fingerprint before/after — set uiChanged accordingly.
|
|
492
|
-
2. If you checked for a specific element with wait_for_ui, set expectedElementVisible.
|
|
493
|
-
3. Do NOT call get_network_activity yet — omit networkRequests on the first call.
|
|
494
|
-
|
|
495
|
-
RULES (applied in order — stop at first match):
|
|
496
|
-
1. If uiChanged=true OR expectedElementVisible=true → outcome=success
|
|
497
|
-
2. Otherwise this tool returns nextAction="call_get_network_activity" — you MUST call get_network_activity once, then call classify_action_outcome again with the results in networkRequests.
|
|
498
|
-
3. If any request has status=failure or retryable → outcome=backend_failure
|
|
499
|
-
4. If no requests returned → outcome=no_op
|
|
500
|
-
5. If all requests succeeded → outcome=ui_failure
|
|
501
|
-
6. Otherwise → outcome=unknown
|
|
502
|
-
|
|
503
|
-
BEHAVIOUR after outcome:
|
|
504
|
-
- success → continue
|
|
505
|
-
- no_op → retry the action once or re-resolve the element
|
|
506
|
-
- backend_failure → stop and report the failing endpoint
|
|
507
|
-
- ui_failure → stop and report failure
|
|
508
|
-
- unknown → take one recovery step (e.g. capture_debug_snapshot), then stop`,
|
|
509
|
-
inputSchema: {
|
|
510
|
-
type: 'object',
|
|
511
|
-
properties: {
|
|
512
|
-
uiChanged: {
|
|
513
|
-
type: 'boolean',
|
|
514
|
-
description: 'true if the screen fingerprint or activity changed after the action. Use wait_for_screen_change or compare get_screen_fingerprint before and after.'
|
|
515
|
-
},
|
|
516
|
-
expectedElementVisible: {
|
|
517
|
-
type: 'boolean',
|
|
518
|
-
description: 'true if the element you expected to appear is now visible (from wait_for_ui). Omit if you did not check for a specific element.'
|
|
519
|
-
},
|
|
520
|
-
networkRequests: {
|
|
521
|
-
type: 'array',
|
|
522
|
-
description: 'Pass this only after calling get_network_activity as instructed by nextAction. Map each request to endpoint + status.',
|
|
523
|
-
items: {
|
|
524
|
-
type: 'object',
|
|
525
|
-
properties: {
|
|
526
|
-
endpoint: { type: 'string', description: 'Request endpoint or full URL' },
|
|
527
|
-
status: { type: 'string', enum: ['success', 'failure', 'retryable'], description: 'Outcome of the request' }
|
|
528
|
-
},
|
|
529
|
-
required: ['endpoint', 'status']
|
|
530
|
-
}
|
|
531
|
-
},
|
|
532
|
-
hasLogErrors: {
|
|
533
|
-
type: 'boolean',
|
|
534
|
-
description: 'true if structured log errors were observed (e.g. from read_log_stream). Optional — include if you have already read logs.'
|
|
535
|
-
}
|
|
536
|
-
},
|
|
537
|
-
required: ['uiChanged']
|
|
538
|
-
}
|
|
539
|
-
},
|
|
540
|
-
{
|
|
541
|
-
name: 'get_network_activity',
|
|
542
|
-
description: `Returns structured network events captured from platform logs since the last action.
|
|
543
|
-
|
|
544
|
-
Call this only when classify_action_outcome returns nextAction="call_get_network_activity".
|
|
545
|
-
Do not call more than once per action.
|
|
546
|
-
|
|
547
|
-
Events are filtered to significant (non-background) requests only.
|
|
548
|
-
Each event includes endpoint, method, statusCode, networkError, status, and durationMs.
|
|
549
|
-
|
|
550
|
-
status values:
|
|
551
|
-
- success: HTTP 2xx or request detected with no error signal
|
|
552
|
-
- failure: HTTP 4xx
|
|
553
|
-
- retryable: HTTP 5xx, network error (timeout, dns_error, tls_error, etc.)
|
|
554
|
-
|
|
555
|
-
Returns { requests: [], count: 0 } when no credible network signals are found.`,
|
|
556
|
-
inputSchema: {
|
|
557
|
-
type: 'object',
|
|
558
|
-
properties: {
|
|
559
|
-
platform: {
|
|
560
|
-
type: 'string',
|
|
561
|
-
enum: ['android', 'ios'],
|
|
562
|
-
description: 'Platform to read network logs from'
|
|
563
|
-
},
|
|
564
|
-
deviceId: {
|
|
565
|
-
type: 'string',
|
|
566
|
-
description: 'Device Serial (Android) or UDID (iOS). Defaults to connected/booted device.'
|
|
567
|
-
}
|
|
568
|
-
},
|
|
569
|
-
required: ['platform']
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
];
|
|
573
|
-
async function handleStartApp(args) {
|
|
574
|
-
const { platform, appId, deviceId } = args;
|
|
575
|
-
ToolsNetwork.notifyActionStart();
|
|
576
|
-
const res = await (platform === 'android' ? new AndroidManage().startApp(appId, deviceId) : new iOSManage().startApp(appId, deviceId));
|
|
577
|
-
const response = {
|
|
578
|
-
device: res.device,
|
|
579
|
-
appStarted: res.appStarted,
|
|
580
|
-
launchTimeMs: res.launchTimeMs
|
|
581
|
-
};
|
|
582
|
-
return wrapResponse(response);
|
|
583
|
-
}
|
|
584
|
-
async function handleTerminateApp(args) {
|
|
585
|
-
const { platform, appId, deviceId } = args;
|
|
586
|
-
const res = await (platform === 'android' ? new AndroidManage().terminateApp(appId, deviceId) : new iOSManage().terminateApp(appId, deviceId));
|
|
587
|
-
const response = { device: res.device, appTerminated: res.appTerminated };
|
|
588
|
-
return wrapResponse(response);
|
|
589
|
-
}
|
|
590
|
-
async function handleRestartApp(args) {
|
|
591
|
-
const { platform, appId, deviceId } = args;
|
|
592
|
-
ToolsNetwork.notifyActionStart();
|
|
593
|
-
const res = await (platform === 'android' ? new AndroidManage().restartApp(appId, deviceId) : new iOSManage().restartApp(appId, deviceId));
|
|
594
|
-
const response = { device: res.device, appRestarted: res.appRestarted, launchTimeMs: res.launchTimeMs };
|
|
595
|
-
return wrapResponse(response);
|
|
596
|
-
}
|
|
597
|
-
async function handleResetAppData(args) {
|
|
598
|
-
const { platform, appId, deviceId } = args;
|
|
599
|
-
const res = await (platform === 'android' ? new AndroidManage().resetAppData(appId, deviceId) : new iOSManage().resetAppData(appId, deviceId));
|
|
600
|
-
const response = { device: res.device, dataCleared: res.dataCleared };
|
|
601
|
-
return wrapResponse(response);
|
|
602
|
-
}
|
|
603
|
-
async function handleInstallApp(args) {
|
|
604
|
-
const { platform, projectType, appPath, deviceId } = args;
|
|
605
|
-
const res = await ToolsManage.installAppHandler({ platform, appPath, deviceId, projectType });
|
|
606
|
-
const response = {
|
|
607
|
-
device: res.device,
|
|
608
|
-
installed: res.installed,
|
|
609
|
-
output: res.output,
|
|
610
|
-
error: res.error
|
|
611
|
-
};
|
|
612
|
-
return wrapResponse(response);
|
|
613
|
-
}
|
|
614
|
-
async function handleBuildApp(args) {
|
|
615
|
-
const { platform, projectType, projectPath, variant } = args;
|
|
616
|
-
const res = await ToolsManage.buildAppHandler({ platform, projectPath, variant, projectType });
|
|
617
|
-
return wrapResponse(res);
|
|
618
|
-
}
|
|
619
|
-
async function handleBuildAndInstall(args) {
|
|
620
|
-
const { platform, projectType, projectPath, deviceId, timeout } = args;
|
|
621
|
-
const res = await ToolsManage.buildAndInstallHandler({ platform, projectPath, deviceId, timeout, projectType });
|
|
622
|
-
return {
|
|
623
|
-
content: [
|
|
624
|
-
{ type: 'text', text: res.ndjson },
|
|
625
|
-
{ type: 'text', text: JSON.stringify(res.result, null, 2) }
|
|
626
|
-
]
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
async function handleGetLogs(args) {
|
|
630
|
-
const { platform, appId, deviceId, pid, tag, level, contains, since_seconds, limit, lines } = args;
|
|
631
|
-
const res = await ToolsObserve.getLogsHandler({ platform, appId, deviceId, pid, tag, level, contains, since_seconds, limit, lines });
|
|
632
|
-
const filtered = !!(pid || tag || level || contains || since_seconds || appId);
|
|
633
|
-
return {
|
|
634
|
-
content: [
|
|
635
|
-
{ type: 'text', text: JSON.stringify({ device: res.device, result: { count: res.logCount, filtered, crashLines: (res.crashLines || []), source: res.source, meta: res.meta || {} } }, null, 2) },
|
|
636
|
-
{ type: 'text', text: JSON.stringify({ logs: res.logs }, null, 2) }
|
|
637
|
-
]
|
|
638
|
-
};
|
|
639
|
-
}
|
|
640
|
-
async function handleListDevices(args) {
|
|
641
|
-
const { platform, appId } = args;
|
|
642
|
-
const res = await ToolsManage.listDevicesHandler({ platform, appId });
|
|
643
|
-
return wrapResponse(res);
|
|
644
|
-
}
|
|
645
|
-
async function handleGetSystemStatus() {
|
|
646
|
-
const result = await getSystemStatus();
|
|
647
|
-
return wrapResponse(result);
|
|
648
|
-
}
|
|
649
|
-
async function handleCaptureScreenshot(args) {
|
|
650
|
-
const { platform, deviceId } = args;
|
|
651
|
-
const res = await ToolsObserve.captureScreenshotHandler({ platform, deviceId });
|
|
652
|
-
const mime = res.screenshot_mime || 'image/png';
|
|
653
|
-
const content = [
|
|
654
|
-
{ type: 'text', text: JSON.stringify({ device: res.device, result: { resolution: res.resolution, mimeType: mime } }, null, 2) },
|
|
655
|
-
{ type: 'image', data: res.screenshot, mimeType: mime }
|
|
656
|
-
];
|
|
657
|
-
if (res.screenshot_fallback) {
|
|
658
|
-
content.push({ type: 'text', text: JSON.stringify({ note: 'JPEG fallback included for compatibility', mimeType: res.screenshot_fallback_mime || 'image/jpeg' }) });
|
|
659
|
-
content.push({ type: 'image', data: res.screenshot_fallback, mimeType: res.screenshot_fallback_mime || 'image/jpeg' });
|
|
660
|
-
}
|
|
661
|
-
return { content };
|
|
662
|
-
}
|
|
663
|
-
async function handleCaptureDebugSnapshot(args) {
|
|
664
|
-
const { reason, includeLogs, logLines, platform, appId, deviceId, sessionId } = args;
|
|
665
|
-
const res = await ToolsObserve.captureDebugSnapshotHandler({ reason, includeLogs, logLines, platform, appId, deviceId, sessionId });
|
|
666
|
-
return wrapResponse(res);
|
|
667
|
-
}
|
|
668
|
-
async function handleGetUITree(args) {
|
|
669
|
-
const { platform, deviceId } = args;
|
|
670
|
-
const res = await ToolsObserve.getUITreeHandler({ platform, deviceId });
|
|
671
|
-
return wrapResponse(res);
|
|
672
|
-
}
|
|
673
|
-
async function handleGetCurrentScreen(args) {
|
|
674
|
-
const { deviceId } = args;
|
|
675
|
-
const res = await ToolsObserve.getCurrentScreenHandler({ deviceId });
|
|
676
|
-
return wrapResponse(res);
|
|
677
|
-
}
|
|
678
|
-
async function handleGetScreenFingerprint(args) {
|
|
679
|
-
const { platform, deviceId } = args;
|
|
680
|
-
const res = await ToolsObserve.getScreenFingerprintHandler({ platform, deviceId });
|
|
681
|
-
return wrapResponse(res);
|
|
682
|
-
}
|
|
683
|
-
async function handleWaitForScreenChange(args) {
|
|
684
|
-
const { platform, previousFingerprint, timeoutMs, pollIntervalMs, deviceId } = args;
|
|
685
|
-
const res = await ToolsInteract.waitForScreenChangeHandler({ platform, previousFingerprint, timeoutMs, pollIntervalMs, deviceId });
|
|
686
|
-
return wrapResponse(res);
|
|
687
|
-
}
|
|
688
|
-
async function handleWaitForUI(args) {
|
|
689
|
-
const { selector, condition = 'exists', timeout_ms = 60000, poll_interval_ms = 300, match, retry, platform, deviceId } = args;
|
|
690
|
-
const res = await ToolsInteract.waitForUIHandler({ selector, condition, timeout_ms, poll_interval_ms, match, retry, platform, deviceId });
|
|
691
|
-
return wrapResponse(res);
|
|
692
|
-
}
|
|
693
|
-
async function handleFindElement(args) {
|
|
694
|
-
const { query, exact = false, timeoutMs = 3000, platform, deviceId } = args;
|
|
695
|
-
const res = await ToolsInteract.findElementHandler({ query, exact, timeoutMs, platform, deviceId });
|
|
696
|
-
return wrapResponse(res);
|
|
697
|
-
}
|
|
698
|
-
async function handleTap(args) {
|
|
699
|
-
const { platform, x, y, deviceId } = args;
|
|
700
|
-
ToolsNetwork.notifyActionStart();
|
|
701
|
-
const res = await ToolsInteract.tapHandler({ platform, x, y, deviceId });
|
|
702
|
-
return wrapResponse(res);
|
|
703
|
-
}
|
|
704
|
-
async function handleTapElement(args) {
|
|
705
|
-
const { elementId } = args;
|
|
706
|
-
ToolsNetwork.notifyActionStart();
|
|
707
|
-
const res = await ToolsInteract.tapElementHandler({ elementId });
|
|
708
|
-
return wrapResponse(res);
|
|
709
|
-
}
|
|
710
|
-
async function handleSwipe(args) {
|
|
711
|
-
const { platform = 'android', x1, y1, x2, y2, duration, deviceId } = args;
|
|
712
|
-
ToolsNetwork.notifyActionStart();
|
|
713
|
-
const res = await ToolsInteract.swipeHandler({ platform, x1, y1, x2, y2, duration, deviceId });
|
|
714
|
-
return wrapResponse(res);
|
|
715
|
-
}
|
|
716
|
-
async function handleScrollToElement(args) {
|
|
717
|
-
const { platform, selector, direction, maxScrolls, scrollAmount, deviceId } = args;
|
|
718
|
-
ToolsNetwork.notifyActionStart();
|
|
719
|
-
const res = await ToolsInteract.scrollToElementHandler({ platform, selector, direction, maxScrolls, scrollAmount, deviceId });
|
|
720
|
-
return wrapResponse(res);
|
|
721
|
-
}
|
|
722
|
-
async function handleTypeText(args) {
|
|
723
|
-
const { text, deviceId } = args;
|
|
724
|
-
ToolsNetwork.notifyActionStart();
|
|
725
|
-
const res = await ToolsInteract.typeTextHandler({ text, deviceId });
|
|
726
|
-
return wrapResponse(res);
|
|
727
|
-
}
|
|
728
|
-
async function handlePressBack(args) {
|
|
729
|
-
const { deviceId } = args;
|
|
730
|
-
ToolsNetwork.notifyActionStart();
|
|
731
|
-
const res = await ToolsInteract.pressBackHandler({ deviceId });
|
|
732
|
-
return wrapResponse(res);
|
|
733
|
-
}
|
|
734
|
-
async function handleStartLogStream(args) {
|
|
735
|
-
const { platform, packageName, level, sessionId, deviceId } = args;
|
|
736
|
-
const res = await ToolsObserve.startLogStreamHandler({ platform, packageName, level, sessionId, deviceId });
|
|
737
|
-
return wrapResponse(res);
|
|
738
|
-
}
|
|
739
|
-
async function handleReadLogStream(args) {
|
|
740
|
-
const { platform, sessionId, limit, since } = args;
|
|
741
|
-
const res = await ToolsObserve.readLogStreamHandler({ platform, sessionId, limit, since });
|
|
742
|
-
return wrapResponse(res);
|
|
743
|
-
}
|
|
744
|
-
async function handleStopLogStream(args) {
|
|
745
|
-
const { platform, sessionId } = args;
|
|
746
|
-
const res = await ToolsObserve.stopLogStreamHandler({ platform, sessionId });
|
|
747
|
-
return wrapResponse(res);
|
|
748
|
-
}
|
|
749
|
-
function handleClassifyActionOutcome(args) {
|
|
750
|
-
const { uiChanged, expectedElementVisible, networkRequests, hasLogErrors } = args;
|
|
751
|
-
const result = classifyActionOutcome({
|
|
752
|
-
uiChanged: Boolean(uiChanged),
|
|
753
|
-
expectedElementVisible: expectedElementVisible ?? null,
|
|
754
|
-
networkRequests: networkRequests ?? null,
|
|
755
|
-
hasLogErrors: hasLogErrors ?? null
|
|
756
|
-
});
|
|
757
|
-
return Promise.resolve(wrapResponse(result));
|
|
758
|
-
}
|
|
759
|
-
async function handleGetNetworkActivity(args) {
|
|
760
|
-
const { platform, deviceId } = args;
|
|
761
|
-
const result = await ToolsNetwork.getNetworkActivity({ platform, deviceId });
|
|
762
|
-
return wrapResponse(result);
|
|
763
|
-
}
|
|
764
|
-
const toolHandlers = {
|
|
765
|
-
start_app: handleStartApp,
|
|
766
|
-
terminate_app: handleTerminateApp,
|
|
767
|
-
restart_app: handleRestartApp,
|
|
768
|
-
reset_app_data: handleResetAppData,
|
|
769
|
-
install_app: handleInstallApp,
|
|
770
|
-
build_app: handleBuildApp,
|
|
771
|
-
build_and_install: handleBuildAndInstall,
|
|
772
|
-
get_logs: handleGetLogs,
|
|
773
|
-
list_devices: handleListDevices,
|
|
774
|
-
get_system_status: handleGetSystemStatus,
|
|
775
|
-
capture_screenshot: handleCaptureScreenshot,
|
|
776
|
-
capture_debug_snapshot: handleCaptureDebugSnapshot,
|
|
777
|
-
get_ui_tree: handleGetUITree,
|
|
778
|
-
get_current_screen: handleGetCurrentScreen,
|
|
779
|
-
get_screen_fingerprint: handleGetScreenFingerprint,
|
|
780
|
-
wait_for_screen_change: handleWaitForScreenChange,
|
|
781
|
-
wait_for_ui: handleWaitForUI,
|
|
782
|
-
find_element: handleFindElement,
|
|
783
|
-
tap: handleTap,
|
|
784
|
-
tap_element: handleTapElement,
|
|
785
|
-
swipe: handleSwipe,
|
|
786
|
-
scroll_to_element: handleScrollToElement,
|
|
787
|
-
type_text: handleTypeText,
|
|
788
|
-
press_back: handlePressBack,
|
|
789
|
-
start_log_stream: handleStartLogStream,
|
|
790
|
-
read_log_stream: handleReadLogStream,
|
|
791
|
-
stop_log_stream: handleStopLogStream,
|
|
792
|
-
classify_action_outcome: handleClassifyActionOutcome,
|
|
793
|
-
get_network_activity: handleGetNetworkActivity
|
|
794
|
-
};
|
|
795
|
-
export async function handleToolCall(name, args = {}) {
|
|
796
|
-
const handler = toolHandlers[name];
|
|
797
|
-
if (!handler)
|
|
798
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
799
|
-
try {
|
|
800
|
-
return await handler(args);
|
|
801
|
-
}
|
|
802
|
-
catch (error) {
|
|
803
|
-
return {
|
|
804
|
-
content: [{ type: 'text', text: `Error executing tool ${name}: ${error instanceof Error ? error.message : String(error)}` }]
|
|
805
|
-
};
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
11
|
export function createServer() {
|
|
809
12
|
const server = new Server(serverInfo, {
|
|
810
13
|
capabilities: {
|