@mobilenext/mobile-mcp 0.0.38 → 0.0.40
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/lib/android.js +2 -2
- package/lib/ios.js +2 -2
- package/lib/iphone-simulator.js +2 -2
- package/lib/mobile-device.js +2 -2
- package/lib/server.js +28 -22
- package/lib/webdriver-agent.js +2 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ This server allows Agents and LLMs to interact with native iOS/Android applicati
|
|
|
28
28
|
<a href="https://github.com/mobile-next/mobile-mcp/wiki">
|
|
29
29
|
<img src="https://img.shields.io/badge/documentation-wiki-blue" alt="wiki" />
|
|
30
30
|
</a>
|
|
31
|
-
<a href="
|
|
31
|
+
<a href="https://mobilenexthq.com/join-slack">
|
|
32
32
|
<img src="https://img.shields.io/badge/join-Slack-blueviolet?logo=slack&style=flat" alt="join on Slack" />
|
|
33
33
|
</a>
|
|
34
34
|
</h4>
|
package/lib/android.js
CHANGED
|
@@ -399,9 +399,9 @@ class AndroidRobot {
|
|
|
399
399
|
async tap(x, y) {
|
|
400
400
|
this.adb("shell", "input", "tap", `${x}`, `${y}`);
|
|
401
401
|
}
|
|
402
|
-
async longPress(x, y) {
|
|
402
|
+
async longPress(x, y, duration) {
|
|
403
403
|
// a long press is a swipe with no movement and a long duration
|
|
404
|
-
this.adb("shell", "input", "swipe", `${x}`, `${y}`, `${x}`, `${y}`,
|
|
404
|
+
this.adb("shell", "input", "swipe", `${x}`, `${y}`, `${x}`, `${y}`, `${duration}`);
|
|
405
405
|
}
|
|
406
406
|
async doubleTap(x, y) {
|
|
407
407
|
await this.tap(x, y);
|
package/lib/ios.js
CHANGED
|
@@ -145,9 +145,9 @@ class IosRobot {
|
|
|
145
145
|
const wda = await this.wda();
|
|
146
146
|
await wda.doubleTap(x, y);
|
|
147
147
|
}
|
|
148
|
-
async longPress(x, y) {
|
|
148
|
+
async longPress(x, y, duration) {
|
|
149
149
|
const wda = await this.wda();
|
|
150
|
-
await wda.longPress(x, y);
|
|
150
|
+
await wda.longPress(x, y, duration);
|
|
151
151
|
}
|
|
152
152
|
async getElementsOnScreen() {
|
|
153
153
|
const wda = await this.wda();
|
package/lib/iphone-simulator.js
CHANGED
|
@@ -192,9 +192,9 @@ class Simctl {
|
|
|
192
192
|
const wda = await this.wda();
|
|
193
193
|
await wda.doubleTap(x, y);
|
|
194
194
|
}
|
|
195
|
-
async longPress(x, y) {
|
|
195
|
+
async longPress(x, y, duration) {
|
|
196
196
|
const wda = await this.wda();
|
|
197
|
-
return wda.longPress(x, y);
|
|
197
|
+
return wda.longPress(x, y, duration);
|
|
198
198
|
}
|
|
199
199
|
async pressButton(button) {
|
|
200
200
|
const wda = await this.wda();
|
package/lib/mobile-device.js
CHANGED
|
@@ -109,8 +109,8 @@ class MobileDevice {
|
|
|
109
109
|
await this.tap(x, y);
|
|
110
110
|
await this.tap(x, y);
|
|
111
111
|
}
|
|
112
|
-
async longPress(x, y) {
|
|
113
|
-
this.runCommand(["io", "longpress", `${x},${y}`]);
|
|
112
|
+
async longPress(x, y, duration) {
|
|
113
|
+
this.runCommand(["io", "longpress", `${x},${y}`, "--duration", `${duration}`]);
|
|
114
114
|
}
|
|
115
115
|
async getElementsOnScreen() {
|
|
116
116
|
const response = JSON.parse(this.runCommand(["dump", "ui"]));
|
package/lib/server.js
CHANGED
|
@@ -39,11 +39,12 @@ const createMcpServer = () => {
|
|
|
39
39
|
return "unknown";
|
|
40
40
|
}
|
|
41
41
|
};
|
|
42
|
-
const tool = (name, title, description, paramsSchema, cb) => {
|
|
42
|
+
const tool = (name, title, description, paramsSchema, annotations, cb) => {
|
|
43
43
|
server.registerTool(name, {
|
|
44
44
|
title,
|
|
45
45
|
description,
|
|
46
46
|
inputSchema: paramsSchema,
|
|
47
|
+
annotations,
|
|
47
48
|
}, (async (args, _extra) => {
|
|
48
49
|
try {
|
|
49
50
|
(0, logger_1.trace)(`Invoking ${name} with args: ${JSON.stringify(args)}`);
|
|
@@ -157,7 +158,7 @@ const createMcpServer = () => {
|
|
|
157
158
|
};
|
|
158
159
|
tool("mobile_list_available_devices", "List Devices", "List all available devices. This includes both physical devices and simulators. If there is more than one device returned, you need to let the user select one of them.", {
|
|
159
160
|
noParams
|
|
160
|
-
}, async ({}) => {
|
|
161
|
+
}, { readOnlyHint: true }, async ({}) => {
|
|
161
162
|
// from today onward, we must have mobilecli working
|
|
162
163
|
ensureMobilecliAvailable();
|
|
163
164
|
const iosManager = new ios_1.IosManager();
|
|
@@ -215,7 +216,7 @@ const createMcpServer = () => {
|
|
|
215
216
|
});
|
|
216
217
|
tool("mobile_list_apps", "List Apps", "List all the installed apps on the device", {
|
|
217
218
|
device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you.")
|
|
218
|
-
}, async ({ device }) => {
|
|
219
|
+
}, { readOnlyHint: true }, async ({ device }) => {
|
|
219
220
|
const robot = getRobotFromDevice(device);
|
|
220
221
|
const result = await robot.listApps();
|
|
221
222
|
return `Found these apps on device: ${result.map(app => `${app.appName} (${app.packageName})`).join(", ")}`;
|
|
@@ -223,7 +224,7 @@ const createMcpServer = () => {
|
|
|
223
224
|
tool("mobile_launch_app", "Launch App", "Launch an app on mobile device. Use this to open a specific app. You can find the package name of the app by calling list_apps_on_device.", {
|
|
224
225
|
device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you."),
|
|
225
226
|
packageName: zod_1.z.string().describe("The package name of the app to launch"),
|
|
226
|
-
}, async ({ device, packageName }) => {
|
|
227
|
+
}, { destructiveHint: true }, async ({ device, packageName }) => {
|
|
227
228
|
const robot = getRobotFromDevice(device);
|
|
228
229
|
await robot.launchApp(packageName);
|
|
229
230
|
return `Launched app ${packageName}`;
|
|
@@ -231,7 +232,7 @@ const createMcpServer = () => {
|
|
|
231
232
|
tool("mobile_terminate_app", "Terminate App", "Stop and terminate an app on mobile device", {
|
|
232
233
|
device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you."),
|
|
233
234
|
packageName: zod_1.z.string().describe("The package name of the app to terminate"),
|
|
234
|
-
}, async ({ device, packageName }) => {
|
|
235
|
+
}, { destructiveHint: true }, async ({ device, packageName }) => {
|
|
235
236
|
const robot = getRobotFromDevice(device);
|
|
236
237
|
await robot.terminateApp(packageName);
|
|
237
238
|
return `Terminated app ${packageName}`;
|
|
@@ -239,7 +240,7 @@ const createMcpServer = () => {
|
|
|
239
240
|
tool("mobile_install_app", "Install App", "Install an app on mobile device", {
|
|
240
241
|
device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you."),
|
|
241
242
|
path: zod_1.z.string().describe("The path to the app file to install. For iOS simulators, provide a .zip file or a .app directory. For Android provide an .apk file. For iOS real devices provide an .ipa file"),
|
|
242
|
-
}, async ({ device, path }) => {
|
|
243
|
+
}, { destructiveHint: true }, async ({ device, path }) => {
|
|
243
244
|
const robot = getRobotFromDevice(device);
|
|
244
245
|
await robot.installApp(path);
|
|
245
246
|
return `Installed app from ${path}`;
|
|
@@ -247,14 +248,14 @@ const createMcpServer = () => {
|
|
|
247
248
|
tool("mobile_uninstall_app", "Uninstall App", "Uninstall an app from mobile device", {
|
|
248
249
|
device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you."),
|
|
249
250
|
bundle_id: zod_1.z.string().describe("Bundle identifier (iOS) or package name (Android) of the app to be uninstalled"),
|
|
250
|
-
}, async ({ device, bundle_id }) => {
|
|
251
|
+
}, { destructiveHint: true }, async ({ device, bundle_id }) => {
|
|
251
252
|
const robot = getRobotFromDevice(device);
|
|
252
253
|
await robot.uninstallApp(bundle_id);
|
|
253
254
|
return `Uninstalled app ${bundle_id}`;
|
|
254
255
|
});
|
|
255
256
|
tool("mobile_get_screen_size", "Get Screen Size", "Get the screen size of the mobile device in pixels", {
|
|
256
257
|
device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you.")
|
|
257
|
-
}, async ({ device }) => {
|
|
258
|
+
}, { readOnlyHint: true }, async ({ device }) => {
|
|
258
259
|
const robot = getRobotFromDevice(device);
|
|
259
260
|
const screenSize = await robot.getScreenSize();
|
|
260
261
|
return `Screen size is ${screenSize.width}x${screenSize.height} pixels`;
|
|
@@ -263,7 +264,7 @@ const createMcpServer = () => {
|
|
|
263
264
|
device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you."),
|
|
264
265
|
x: zod_1.z.number().describe("The x coordinate to click on the screen, in pixels"),
|
|
265
266
|
y: zod_1.z.number().describe("The y coordinate to click on the screen, in pixels"),
|
|
266
|
-
}, async ({ device, x, y }) => {
|
|
267
|
+
}, { destructiveHint: true }, async ({ device, x, y }) => {
|
|
267
268
|
const robot = getRobotFromDevice(device);
|
|
268
269
|
await robot.tap(x, y);
|
|
269
270
|
return `Clicked on screen at coordinates: ${x}, ${y}`;
|
|
@@ -272,7 +273,7 @@ const createMcpServer = () => {
|
|
|
272
273
|
device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you."),
|
|
273
274
|
x: zod_1.z.number().describe("The x coordinate to double-tap, in pixels"),
|
|
274
275
|
y: zod_1.z.number().describe("The y coordinate to double-tap, in pixels"),
|
|
275
|
-
}, async ({ device, x, y }) => {
|
|
276
|
+
}, { destructiveHint: true }, async ({ device, x, y }) => {
|
|
276
277
|
const robot = getRobotFromDevice(device);
|
|
277
278
|
await robot.doubleTap(x, y);
|
|
278
279
|
return `Double-tapped on screen at coordinates: ${x}, ${y}`;
|
|
@@ -281,14 +282,16 @@ const createMcpServer = () => {
|
|
|
281
282
|
device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you."),
|
|
282
283
|
x: zod_1.z.number().describe("The x coordinate to long press on the screen, in pixels"),
|
|
283
284
|
y: zod_1.z.number().describe("The y coordinate to long press on the screen, in pixels"),
|
|
284
|
-
|
|
285
|
+
duration: zod_1.z.number().min(1).max(10000).optional().describe("Duration of the long press in milliseconds. Defaults to 500ms."),
|
|
286
|
+
}, { destructiveHint: true }, async ({ device, x, y, duration }) => {
|
|
285
287
|
const robot = getRobotFromDevice(device);
|
|
286
|
-
|
|
287
|
-
|
|
288
|
+
const pressDuration = duration ?? 500;
|
|
289
|
+
await robot.longPress(x, y, pressDuration);
|
|
290
|
+
return `Long pressed on screen at coordinates: ${x}, ${y} for ${pressDuration}ms`;
|
|
288
291
|
});
|
|
289
292
|
tool("mobile_list_elements_on_screen", "List Screen Elements", "List elements on screen and their coordinates, with display text or accessibility label. Do not cache this result.", {
|
|
290
293
|
device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you.")
|
|
291
|
-
}, async ({ device }) => {
|
|
294
|
+
}, { readOnlyHint: true }, async ({ device }) => {
|
|
292
295
|
const robot = getRobotFromDevice(device);
|
|
293
296
|
const elements = await robot.getElementsOnScreen();
|
|
294
297
|
const result = elements.map(element => {
|
|
@@ -316,7 +319,7 @@ const createMcpServer = () => {
|
|
|
316
319
|
tool("mobile_press_button", "Press Button", "Press a button on device", {
|
|
317
320
|
device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you."),
|
|
318
321
|
button: zod_1.z.string().describe("The button to press. Supported buttons: BACK (android only), HOME, VOLUME_UP, VOLUME_DOWN, ENTER, DPAD_CENTER (android tv only), DPAD_UP (android tv only), DPAD_DOWN (android tv only), DPAD_LEFT (android tv only), DPAD_RIGHT (android tv only)"),
|
|
319
|
-
}, async ({ device, button }) => {
|
|
322
|
+
}, { destructiveHint: true }, async ({ device, button }) => {
|
|
320
323
|
const robot = getRobotFromDevice(device);
|
|
321
324
|
await robot.pressButton(button);
|
|
322
325
|
return `Pressed the button: ${button}`;
|
|
@@ -324,7 +327,7 @@ const createMcpServer = () => {
|
|
|
324
327
|
tool("mobile_open_url", "Open URL", "Open a URL in browser on device", {
|
|
325
328
|
device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you."),
|
|
326
329
|
url: zod_1.z.string().describe("The URL to open"),
|
|
327
|
-
}, async ({ device, url }) => {
|
|
330
|
+
}, { destructiveHint: true }, async ({ device, url }) => {
|
|
328
331
|
const robot = getRobotFromDevice(device);
|
|
329
332
|
await robot.openUrl(url);
|
|
330
333
|
return `Opened URL: ${url}`;
|
|
@@ -335,7 +338,7 @@ const createMcpServer = () => {
|
|
|
335
338
|
x: zod_1.z.number().optional().describe("The x coordinate to start the swipe from, in pixels. If not provided, uses center of screen"),
|
|
336
339
|
y: zod_1.z.number().optional().describe("The y coordinate to start the swipe from, in pixels. If not provided, uses center of screen"),
|
|
337
340
|
distance: zod_1.z.number().optional().describe("The distance to swipe in pixels. Defaults to 400 pixels for iOS or 30% of screen dimension for Android"),
|
|
338
|
-
}, async ({ device, direction, x, y, distance }) => {
|
|
341
|
+
}, { destructiveHint: true }, async ({ device, direction, x, y, distance }) => {
|
|
339
342
|
const robot = getRobotFromDevice(device);
|
|
340
343
|
if (x !== undefined && y !== undefined) {
|
|
341
344
|
// Use coordinate-based swipe
|
|
@@ -353,7 +356,7 @@ const createMcpServer = () => {
|
|
|
353
356
|
device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you."),
|
|
354
357
|
text: zod_1.z.string().describe("The text to type"),
|
|
355
358
|
submit: zod_1.z.boolean().describe("Whether to submit the text. If true, the text will be submitted as if the user pressed the enter key."),
|
|
356
|
-
}, async ({ device, text, submit }) => {
|
|
359
|
+
}, { destructiveHint: true }, async ({ device, text, submit }) => {
|
|
357
360
|
const robot = getRobotFromDevice(device);
|
|
358
361
|
await robot.sendKeys(text);
|
|
359
362
|
if (submit) {
|
|
@@ -364,7 +367,7 @@ const createMcpServer = () => {
|
|
|
364
367
|
tool("mobile_save_screenshot", "Save Screenshot", "Save a screenshot of the mobile device to a file", {
|
|
365
368
|
device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you."),
|
|
366
369
|
saveTo: zod_1.z.string().describe("The path to save the screenshot to"),
|
|
367
|
-
}, async ({ device, saveTo }) => {
|
|
370
|
+
}, { destructiveHint: true }, async ({ device, saveTo }) => {
|
|
368
371
|
const robot = getRobotFromDevice(device);
|
|
369
372
|
const screenshot = await robot.getScreenshot();
|
|
370
373
|
node_fs_1.default.writeFileSync(saveTo, screenshot);
|
|
@@ -375,7 +378,10 @@ const createMcpServer = () => {
|
|
|
375
378
|
description: "Take a screenshot of the mobile device. Use this to understand what's on screen, if you need to press an element that is available through view hierarchy then you must list elements on screen instead. Do not cache this result.",
|
|
376
379
|
inputSchema: {
|
|
377
380
|
device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you.")
|
|
378
|
-
}
|
|
381
|
+
},
|
|
382
|
+
annotations: {
|
|
383
|
+
readOnlyHint: true,
|
|
384
|
+
},
|
|
379
385
|
}, async ({ device }) => {
|
|
380
386
|
try {
|
|
381
387
|
const robot = getRobotFromDevice(device);
|
|
@@ -423,14 +429,14 @@ const createMcpServer = () => {
|
|
|
423
429
|
tool("mobile_set_orientation", "Set Orientation", "Change the screen orientation of the device", {
|
|
424
430
|
device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you."),
|
|
425
431
|
orientation: zod_1.z.enum(["portrait", "landscape"]).describe("The desired orientation"),
|
|
426
|
-
}, async ({ device, orientation }) => {
|
|
432
|
+
}, { destructiveHint: true }, async ({ device, orientation }) => {
|
|
427
433
|
const robot = getRobotFromDevice(device);
|
|
428
434
|
await robot.setOrientation(orientation);
|
|
429
435
|
return `Changed device orientation to ${orientation}`;
|
|
430
436
|
});
|
|
431
437
|
tool("mobile_get_orientation", "Get Orientation", "Get the current screen orientation of the device", {
|
|
432
438
|
device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you.")
|
|
433
|
-
}, async ({ device }) => {
|
|
439
|
+
}, { readOnlyHint: true }, async ({ device }) => {
|
|
434
440
|
const robot = getRobotFromDevice(device);
|
|
435
441
|
const orientation = await robot.getOrientation();
|
|
436
442
|
return `Current device orientation is ${orientation}`;
|
package/lib/webdriver-agent.js
CHANGED
|
@@ -172,7 +172,7 @@ class WebDriverAgent {
|
|
|
172
172
|
});
|
|
173
173
|
});
|
|
174
174
|
}
|
|
175
|
-
async longPress(x, y) {
|
|
175
|
+
async longPress(x, y, duration) {
|
|
176
176
|
await this.withinSession(async (sessionUrl) => {
|
|
177
177
|
const url = `${sessionUrl}/actions`;
|
|
178
178
|
await fetch(url, {
|
|
@@ -189,7 +189,7 @@ class WebDriverAgent {
|
|
|
189
189
|
actions: [
|
|
190
190
|
{ type: "pointerMove", duration: 0, x, y },
|
|
191
191
|
{ type: "pointerDown", button: 0 },
|
|
192
|
-
{ type: "pause", duration
|
|
192
|
+
{ type: "pause", duration },
|
|
193
193
|
{ type: "pointerUp", button: 0 }
|
|
194
194
|
]
|
|
195
195
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mobilenext/mobile-mcp",
|
|
3
3
|
"mcpName": "io.github.mobile-next/mobile-mcp",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.40",
|
|
5
5
|
"description": "Mobile MCP",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"lib"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@modelcontextprotocol/sdk": "1.
|
|
27
|
+
"@modelcontextprotocol/sdk": "1.25.2",
|
|
28
28
|
"commander": "14.0.0",
|
|
29
29
|
"express": "5.1.0",
|
|
30
30
|
"fast-xml-parser": "5.2.5",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"zod-to-json-schema": "3.25.0"
|
|
33
33
|
},
|
|
34
34
|
"optionalDependencies": {
|
|
35
|
-
"@mobilenext/mobilecli": "0.0.
|
|
35
|
+
"@mobilenext/mobilecli": "0.0.49"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@eslint/eslintrc": "^3.2.0",
|