@mobilenext/mobile-mcp 0.0.27 → 0.0.28

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.
Files changed (2) hide show
  1. package/lib/server.js +60 -81
  2. package/package.json +1 -1
package/lib/server.js CHANGED
@@ -111,44 +111,30 @@ const createMcpServer = () => {
111
111
  }
112
112
  };
113
113
  posthog("launch", {}).then();
114
- let robot;
115
114
  const simulatorManager = new iphone_simulator_1.SimctlManager();
116
- const requireRobot = () => {
117
- if (!robot) {
118
- throw new robot_1.ActionableError("No device selected. Use the mobile_use_device tool to select a device.");
119
- }
120
- };
121
- tool("mobile_use_default_device", "Use the default device. This is a shortcut for mobile_use_device with deviceType=simulator and device=simulator_name", {
122
- noParams
123
- }, async () => {
115
+ const getRobotFromDevice = (device) => {
124
116
  const iosManager = new ios_1.IosManager();
125
117
  const androidManager = new android_1.AndroidDeviceManager();
126
118
  const simulators = simulatorManager.listBootedSimulators();
127
119
  const androidDevices = androidManager.getConnectedDevices();
128
120
  const iosDevices = iosManager.listDevices();
129
- const sum = simulators.length + androidDevices.length + iosDevices.length;
130
- if (sum === 0) {
131
- throw new robot_1.ActionableError("No devices found. Please connect a device and try again.");
132
- }
133
- else if (sum >= 2) {
134
- throw new robot_1.ActionableError("Multiple devices found. Please use the mobile_list_available_devices tool to list available devices and select one.");
135
- }
136
- // only one device connected, let's find it now
137
- if (simulators.length === 1) {
138
- robot = simulatorManager.getSimulator(simulators[0].name);
139
- return `Selected default device: ${simulators[0].name}`;
121
+ // Check if it's a simulator
122
+ const simulator = simulators.find(s => s.name === device);
123
+ if (simulator) {
124
+ return simulatorManager.getSimulator(device);
140
125
  }
141
- else if (androidDevices.length === 1) {
142
- robot = new android_1.AndroidRobot(androidDevices[0].deviceId);
143
- return `Selected default device: ${androidDevices[0].deviceId}`;
126
+ // Check if it's an Android device
127
+ const androidDevice = androidDevices.find(d => d.deviceId === device);
128
+ if (androidDevice) {
129
+ return new android_1.AndroidRobot(device);
144
130
  }
145
- else if (iosDevices.length === 1) {
146
- robot = new ios_1.IosRobot(iosDevices[0].deviceId);
147
- return `Selected default device: ${iosDevices[0].deviceId}`;
131
+ // Check if it's an iOS device
132
+ const iosDevice = iosDevices.find(d => d.deviceId === device);
133
+ if (iosDevice) {
134
+ return new ios_1.IosRobot(device);
148
135
  }
149
- // how did this happen?
150
- throw new robot_1.ActionableError("No device selected. Please use the mobile_list_available_devices tool to list available devices and select one.");
151
- });
136
+ throw new robot_1.ActionableError(`Device "${device}" not found. Use the mobile_list_available_devices tool to see available devices.`);
137
+ };
152
138
  tool("mobile_list_available_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.", {
153
139
  noParams
154
140
  }, async ({}) => {
@@ -176,71 +162,58 @@ const createMcpServer = () => {
176
162
  }
177
163
  return resp.join("\n");
178
164
  });
179
- tool("mobile_use_device", "Select a device to use. This can be a simulator or an Android device. Use the list_available_devices tool to get a list of available devices.", {
180
- device: zod_1.z.string().describe("The name of the device to select"),
181
- deviceType: zod_1.z.enum(["simulator", "ios", "android"]).describe("The type of device to select"),
182
- }, async ({ device, deviceType }) => {
183
- switch (deviceType) {
184
- case "simulator":
185
- robot = simulatorManager.getSimulator(device);
186
- break;
187
- case "ios":
188
- robot = new ios_1.IosRobot(device);
189
- break;
190
- case "android":
191
- robot = new android_1.AndroidRobot(device);
192
- break;
193
- }
194
- return `Selected device: ${device}`;
195
- });
196
165
  tool("mobile_list_apps", "List all the installed apps on the device", {
197
- noParams
198
- }, async ({}) => {
199
- requireRobot();
166
+ device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you.")
167
+ }, async ({ device }) => {
168
+ const robot = getRobotFromDevice(device);
200
169
  const result = await robot.listApps();
201
170
  return `Found these apps on device: ${result.map(app => `${app.appName} (${app.packageName})`).join(", ")}`;
202
171
  });
203
172
  tool("mobile_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.", {
173
+ device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you."),
204
174
  packageName: zod_1.z.string().describe("The package name of the app to launch"),
205
- }, async ({ packageName }) => {
206
- requireRobot();
175
+ }, async ({ device, packageName }) => {
176
+ const robot = getRobotFromDevice(device);
207
177
  await robot.launchApp(packageName);
208
178
  return `Launched app ${packageName}`;
209
179
  });
210
180
  tool("mobile_terminate_app", "Stop and terminate an app on mobile device", {
181
+ device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you."),
211
182
  packageName: zod_1.z.string().describe("The package name of the app to terminate"),
212
- }, async ({ packageName }) => {
213
- requireRobot();
183
+ }, async ({ device, packageName }) => {
184
+ const robot = getRobotFromDevice(device);
214
185
  await robot.terminateApp(packageName);
215
186
  return `Terminated app ${packageName}`;
216
187
  });
217
188
  tool("mobile_get_screen_size", "Get the screen size of the mobile device in pixels", {
218
- noParams
219
- }, async ({}) => {
220
- requireRobot();
189
+ device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you.")
190
+ }, async ({ device }) => {
191
+ const robot = getRobotFromDevice(device);
221
192
  const screenSize = await robot.getScreenSize();
222
193
  return `Screen size is ${screenSize.width}x${screenSize.height} pixels`;
223
194
  });
224
195
  tool("mobile_click_on_screen_at_coordinates", "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.", {
196
+ 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
197
  x: zod_1.z.number().describe("The x coordinate to click on the screen, in pixels"),
226
198
  y: zod_1.z.number().describe("The y coordinate to click on the screen, in pixels"),
227
- }, async ({ x, y }) => {
228
- requireRobot();
199
+ }, async ({ device, x, y }) => {
200
+ const robot = getRobotFromDevice(device);
229
201
  await robot.tap(x, y);
230
202
  return `Clicked on screen at coordinates: ${x}, ${y}`;
231
203
  });
232
204
  tool("mobile_long_press_on_screen_at_coordinates", "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.", {
205
+ 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
206
  x: zod_1.z.number().describe("The x coordinate to long press on the screen, in pixels"),
234
207
  y: zod_1.z.number().describe("The y coordinate to long press on the screen, in pixels"),
235
- }, async ({ x, y }) => {
236
- requireRobot();
208
+ }, async ({ device, x, y }) => {
209
+ const robot = getRobotFromDevice(device);
237
210
  await robot.longPress(x, y);
238
211
  return `Long pressed on screen at coordinates: ${x}, ${y}`;
239
212
  });
240
213
  tool("mobile_list_elements_on_screen", "List elements on screen and their coordinates, with display text or accessibility label. Do not cache this result.", {
241
- noParams
242
- }, async ({}) => {
243
- requireRobot();
214
+ device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you.")
215
+ }, async ({ device }) => {
216
+ const robot = getRobotFromDevice(device);
244
217
  const elements = await robot.getElementsOnScreen();
245
218
  const result = elements.map(element => {
246
219
  const out = {
@@ -265,26 +238,29 @@ const createMcpServer = () => {
265
238
  return `Found these elements on screen: ${JSON.stringify(result)}`;
266
239
  });
267
240
  tool("mobile_press_button", "Press a button on device", {
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."),
268
242
  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)"),
269
- }, async ({ button }) => {
270
- requireRobot();
243
+ }, async ({ device, button }) => {
244
+ const robot = getRobotFromDevice(device);
271
245
  await robot.pressButton(button);
272
246
  return `Pressed the button: ${button}`;
273
247
  });
274
248
  tool("mobile_open_url", "Open a URL in browser on device", {
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."),
275
250
  url: zod_1.z.string().describe("The URL to open"),
276
- }, async ({ url }) => {
277
- requireRobot();
251
+ }, async ({ device, url }) => {
252
+ const robot = getRobotFromDevice(device);
278
253
  await robot.openUrl(url);
279
254
  return `Opened URL: ${url}`;
280
255
  });
281
256
  tool("swipe_on_screen", "Swipe on the screen", {
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."),
282
258
  direction: zod_1.z.enum(["up", "down", "left", "right"]).describe("The direction to swipe"),
283
259
  x: zod_1.z.number().optional().describe("The x coordinate to start the swipe from, in pixels. If not provided, uses center of screen"),
284
260
  y: zod_1.z.number().optional().describe("The y coordinate to start the swipe from, in pixels. If not provided, uses center of screen"),
285
261
  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"),
286
- }, async ({ direction, x, y, distance }) => {
287
- requireRobot();
262
+ }, async ({ device, direction, x, y, distance }) => {
263
+ const robot = getRobotFromDevice(device);
288
264
  if (x !== undefined && y !== undefined) {
289
265
  // Use coordinate-based swipe
290
266
  await robot.swipeFromCoordinate(x, y, direction, distance);
@@ -298,10 +274,11 @@ const createMcpServer = () => {
298
274
  }
299
275
  });
300
276
  tool("mobile_type_keys", "Type text into the focused element", {
277
+ device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you."),
301
278
  text: zod_1.z.string().describe("The text to type"),
302
279
  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."),
303
- }, async ({ text, submit }) => {
304
- requireRobot();
280
+ }, async ({ device, text, submit }) => {
281
+ const robot = getRobotFromDevice(device);
305
282
  await robot.sendKeys(text);
306
283
  if (submit) {
307
284
  await robot.pressButton("ENTER");
@@ -309,18 +286,19 @@ const createMcpServer = () => {
309
286
  return `Typed text: ${text}`;
310
287
  });
311
288
  tool("mobile_save_screenshot", "Save a screenshot of the mobile device to a file", {
289
+ device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you."),
312
290
  saveTo: zod_1.z.string().describe("The path to save the screenshot to"),
313
- }, async ({ saveTo }) => {
314
- requireRobot();
291
+ }, async ({ device, saveTo }) => {
292
+ const robot = getRobotFromDevice(device);
315
293
  const screenshot = await robot.getScreenshot();
316
294
  node_fs_1.default.writeFileSync(saveTo, screenshot);
317
295
  return `Screenshot saved to: ${saveTo}`;
318
296
  });
319
297
  server.tool("mobile_take_screenshot", "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.", {
320
- noParams
321
- }, async ({}) => {
322
- requireRobot();
298
+ device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you.")
299
+ }, async ({ device }) => {
323
300
  try {
301
+ const robot = getRobotFromDevice(device);
324
302
  const screenSize = await robot.getScreenSize();
325
303
  let screenshot = await robot.getScreenshot();
326
304
  let mimeType = "image/png";
@@ -356,16 +334,17 @@ const createMcpServer = () => {
356
334
  }
357
335
  });
358
336
  tool("mobile_set_orientation", "Change the screen orientation of the device", {
337
+ device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you."),
359
338
  orientation: zod_1.z.enum(["portrait", "landscape"]).describe("The desired orientation"),
360
- }, async ({ orientation }) => {
361
- requireRobot();
339
+ }, async ({ device, orientation }) => {
340
+ const robot = getRobotFromDevice(device);
362
341
  await robot.setOrientation(orientation);
363
342
  return `Changed device orientation to ${orientation}`;
364
343
  });
365
344
  tool("mobile_get_orientation", "Get the current screen orientation of the device", {
366
- noParams
367
- }, async () => {
368
- requireRobot();
345
+ device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you.")
346
+ }, async ({ device }) => {
347
+ const robot = getRobotFromDevice(device);
369
348
  const orientation = await robot.getOrientation();
370
349
  return `Current device orientation is ${orientation}`;
371
350
  });
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.27",
4
+ "version": "0.0.28",
5
5
  "description": "Mobile MCP",
6
6
  "repository": {
7
7
  "type": "git",