@mobilenext/mobile-mcp 0.0.46 → 0.0.47

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/lib/android.js CHANGED
@@ -137,8 +137,17 @@ class AndroidRobot {
137
137
  .filter(line => line.startsWith("package:"))
138
138
  .map(line => line.substring("package:".length));
139
139
  }
140
- async launchApp(packageName) {
140
+ async launchApp(packageName, locale) {
141
141
  (0, utils_1.validatePackageName)(packageName);
142
+ if (locale) {
143
+ (0, utils_1.validateLocale)(locale);
144
+ try {
145
+ this.silentAdb("shell", "cmd", "locale", "set-app-locales", packageName, "--locales", locale);
146
+ }
147
+ catch (error) {
148
+ // set-app-locales requires Android 13+ (API 33), silently ignore on older versions
149
+ }
150
+ }
142
151
  try {
143
152
  this.silentAdb("shell", "monkey", "-p", packageName, "-c", "android.intent.category.LAUNCHER", "1");
144
153
  }
package/lib/ios.js CHANGED
@@ -94,10 +94,17 @@ class IosRobot {
94
94
  };
95
95
  });
96
96
  }
97
- async launchApp(packageName) {
97
+ async launchApp(packageName, locale) {
98
98
  (0, utils_1.validatePackageName)(packageName);
99
99
  await this.assertTunnelRunning();
100
- await this.ios("launch", packageName);
100
+ const args = ["launch", packageName];
101
+ if (locale) {
102
+ (0, utils_1.validateLocale)(locale);
103
+ const locales = locale.split(",").map(l => l.trim());
104
+ args.push("-AppleLanguages", `(${locales.join(", ")})`);
105
+ args.push("-AppleLocale", locales[0]);
106
+ }
107
+ await this.ios(...args);
101
108
  }
102
109
  async terminateApp(packageName) {
103
110
  (0, utils_1.validatePackageName)(packageName);
@@ -71,9 +71,16 @@ class Simctl {
71
71
  await wda.openUrl(url);
72
72
  // alternative: this.simctl("openurl", this.simulatorUuid, url);
73
73
  }
74
- async launchApp(packageName) {
74
+ async launchApp(packageName, locale) {
75
75
  (0, utils_1.validatePackageName)(packageName);
76
- this.simctl("launch", this.simulatorUuid, packageName);
76
+ const args = ["launch", this.simulatorUuid, packageName];
77
+ if (locale) {
78
+ (0, utils_1.validateLocale)(locale);
79
+ const locales = locale.split(",").map(l => l.trim());
80
+ args.push("-AppleLanguages", `(${locales.join(", ")})`);
81
+ args.push("-AppleLocale", locales[0]);
82
+ }
83
+ this.simctl(...args);
77
84
  }
78
85
  async terminateApp(packageName) {
79
86
  (0, utils_1.validatePackageName)(packageName);
@@ -80,8 +80,12 @@ class MobileDevice {
80
80
  packageName: app.packageName,
81
81
  }));
82
82
  }
83
- async launchApp(packageName) {
84
- this.runCommand(["apps", "launch", packageName]);
83
+ async launchApp(packageName, locale) {
84
+ const args = ["apps", "launch", packageName];
85
+ if (locale) {
86
+ args.push("--locale", locale);
87
+ }
88
+ this.runCommand(args);
85
89
  }
86
90
  async terminateApp(packageName) {
87
91
  this.runCommand(["apps", "terminate", packageName]);
package/lib/server.js CHANGED
@@ -243,9 +243,10 @@ const createMcpServer = () => {
243
243
  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.", {
244
244
  device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you."),
245
245
  packageName: zod_1.z.string().describe("The package name of the app to launch"),
246
- }, { destructiveHint: true }, async ({ device, packageName }) => {
246
+ locale: zod_1.z.string().optional().describe("Comma-separated BCP 47 locale tags to launch the app with (e.g., fr-FR,en-GB)"),
247
+ }, { destructiveHint: true }, async ({ device, packageName, locale }) => {
247
248
  const robot = getRobotFromDevice(device);
248
- await robot.launchApp(packageName);
249
+ await robot.launchApp(packageName, locale);
249
250
  return `Launched app ${packageName}`;
250
251
  });
251
252
  tool("mobile_terminate_app", "Terminate App", "Stop and terminate an app on mobile device", {
@@ -281,8 +282,8 @@ const createMcpServer = () => {
281
282
  });
282
283
  tool("mobile_click_on_screen_at_coordinates", "Click Screen", "Click on the screen at given x,y coordinates. If clicking on an element, use the list_elements_on_screen tool to find the coordinates.", {
283
284
  device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you."),
284
- x: zod_1.z.number().describe("The x coordinate to click on the screen, in pixels"),
285
- y: zod_1.z.number().describe("The y coordinate to click on the screen, in pixels"),
285
+ x: zod_1.z.coerce.number().describe("The x coordinate to click on the screen, in pixels"),
286
+ y: zod_1.z.coerce.number().describe("The y coordinate to click on the screen, in pixels"),
286
287
  }, { destructiveHint: true }, async ({ device, x, y }) => {
287
288
  const robot = getRobotFromDevice(device);
288
289
  await robot.tap(x, y);
@@ -290,8 +291,8 @@ const createMcpServer = () => {
290
291
  });
291
292
  tool("mobile_double_tap_on_screen", "Double Tap Screen", "Double-tap on the screen at given x,y coordinates.", {
292
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."),
293
- x: zod_1.z.number().describe("The x coordinate to double-tap, in pixels"),
294
- y: zod_1.z.number().describe("The y coordinate to double-tap, in pixels"),
294
+ x: zod_1.z.coerce.number().describe("The x coordinate to double-tap, in pixels"),
295
+ y: zod_1.z.coerce.number().describe("The y coordinate to double-tap, in pixels"),
295
296
  }, { destructiveHint: true }, async ({ device, x, y }) => {
296
297
  const robot = getRobotFromDevice(device);
297
298
  await robot.doubleTap(x, y);
@@ -299,9 +300,9 @@ const createMcpServer = () => {
299
300
  });
300
301
  tool("mobile_long_press_on_screen_at_coordinates", "Long Press Screen", "Long press on the screen at given x,y coordinates. If long pressing on an element, use the list_elements_on_screen tool to find the coordinates.", {
301
302
  device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you."),
302
- x: zod_1.z.number().describe("The x coordinate to long press on the screen, in pixels"),
303
- y: zod_1.z.number().describe("The y coordinate to long press on the screen, in pixels"),
304
- duration: zod_1.z.number().min(1).max(10000).optional().describe("Duration of the long press in milliseconds. Defaults to 500ms."),
303
+ x: zod_1.z.coerce.number().describe("The x coordinate to long press on the screen, in pixels"),
304
+ y: zod_1.z.coerce.number().describe("The y coordinate to long press on the screen, in pixels"),
305
+ duration: zod_1.z.coerce.number().min(1).max(10000).optional().describe("Duration of the long press in milliseconds. Defaults to 500ms."),
305
306
  }, { destructiveHint: true }, async ({ device, x, y, duration }) => {
306
307
  const robot = getRobotFromDevice(device);
307
308
  const pressDuration = duration ?? 500;
@@ -354,9 +355,9 @@ const createMcpServer = () => {
354
355
  tool("mobile_swipe_on_screen", "Swipe Screen", "Swipe on the screen", {
355
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."),
356
357
  direction: zod_1.z.enum(["up", "down", "left", "right"]).describe("The direction to swipe"),
357
- x: zod_1.z.number().optional().describe("The x coordinate to start the swipe from, in pixels. If not provided, uses center of screen"),
358
- y: zod_1.z.number().optional().describe("The y coordinate to start the swipe from, in pixels. If not provided, uses center of screen"),
359
- 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"),
358
+ x: zod_1.z.coerce.number().optional().describe("The x coordinate to start the swipe from, in pixels. If not provided, uses center of screen"),
359
+ y: zod_1.z.coerce.number().optional().describe("The y coordinate to start the swipe from, in pixels. If not provided, uses center of screen"),
360
+ distance: zod_1.z.coerce.number().optional().describe("The distance to swipe in pixels. Defaults to 400 pixels for iOS or 30% of screen dimension for Android"),
360
361
  }, { destructiveHint: true }, async ({ device, direction, x, y, distance }) => {
361
362
  const robot = getRobotFromDevice(device);
362
363
  if (x !== undefined && y !== undefined) {
@@ -463,7 +464,7 @@ const createMcpServer = () => {
463
464
  tool("mobile_start_screen_recording", "Start Screen Recording", "Start recording the screen of a mobile device. The recording runs in the background until stopped with mobile_stop_screen_recording. Returns the path where the recording will be saved.", {
464
465
  device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you."),
465
466
  output: zod_1.z.string().optional().describe("The file path to save the recording to. If not provided, a temporary path will be used."),
466
- timeLimit: zod_1.z.number().optional().describe("Maximum recording duration in seconds. The recording will stop automatically after this time."),
467
+ timeLimit: zod_1.z.coerce.number().optional().describe("Maximum recording duration in seconds. The recording will stop automatically after this time."),
467
468
  }, { destructiveHint: true }, async ({ device, output, timeLimit }) => {
468
469
  getRobotFromDevice(device);
469
470
  if (activeRecordings.has(device)) {
package/lib/utils.js CHANGED
@@ -1,9 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validatePackageName = validatePackageName;
4
+ exports.validateLocale = validateLocale;
4
5
  const robot_1 = require("./robot");
5
6
  function validatePackageName(packageName) {
6
7
  if (!/^[a-zA-Z0-9._]+$/.test(packageName)) {
7
8
  throw new robot_1.ActionableError(`Invalid package name: "${packageName}"`);
8
9
  }
9
10
  }
11
+ function validateLocale(locale) {
12
+ if (!/^[a-zA-Z0-9,\- ]+$/.test(locale)) {
13
+ throw new robot_1.ActionableError(`Invalid locale: "${locale}"`);
14
+ }
15
+ }
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.46",
4
+ "version": "0.0.47",
5
5
  "description": "Mobile MCP",
6
6
  "repository": {
7
7
  "type": "git",