@mobilenext/mobile-mcp 0.0.22 → 0.0.24

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
@@ -190,8 +190,42 @@ class AndroidRobot {
190
190
  }
191
191
  this.adb("shell", "input", "swipe", `${x0}`, `${y0}`, `${x1}`, `${y1}`, "1000");
192
192
  }
193
+ getDisplayCount() {
194
+ return this.adb("shell", "dumpsys", "SurfaceFlinger", "--display-id")
195
+ .toString()
196
+ .split("\n")
197
+ .filter(s => s.startsWith("Display "))
198
+ .length;
199
+ }
200
+ getFirstDisplayId() {
201
+ const displays = this.adb("shell", "cmd", "display", "get-displays")
202
+ .toString()
203
+ .split("\n")
204
+ .filter(s => s.startsWith("Display id "))
205
+ // filter for state ON even though get-displays only returns turned on displays
206
+ .filter(s => s.indexOf(", state ON,") >= 0)
207
+ // another paranoia check
208
+ .filter(s => s.indexOf(", uniqueId ") >= 0);
209
+ if (displays.length > 0) {
210
+ const m = displays[0].match(/uniqueId \"([^\"]+)\"/);
211
+ if (m !== null) {
212
+ const displayId = m[1];
213
+ if (displayId.indexOf("local:") === 0) {
214
+ return displayId.split(":")[1];
215
+ }
216
+ return displayId;
217
+ }
218
+ }
219
+ return null;
220
+ }
193
221
  async getScreenshot() {
194
- return this.adb("exec-out", "screencap", "-p");
222
+ if (this.getDisplayCount() <= 1) {
223
+ // backward compatibility for android 10 and below, and for single display devices
224
+ return this.adb("exec-out", "screencap", "-p");
225
+ }
226
+ // find the first display that is turned on, and capture that one
227
+ const displayId = this.getFirstDisplayId();
228
+ return this.adb("exec-out", "screencap", "-p", "-d", `${displayId}`);
195
229
  }
196
230
  collectElements(node) {
197
231
  const elements = [];
@@ -274,16 +308,21 @@ class AndroidRobot {
274
308
  if (!BUTTON_MAP[button]) {
275
309
  throw new robot_1.ActionableError(`Button "${button}" is not supported`);
276
310
  }
277
- this.adb("shell", "input", "keyevent", BUTTON_MAP[button]);
311
+ const mapped = BUTTON_MAP[button];
312
+ this.adb("shell", "input", "keyevent", mapped);
278
313
  }
279
314
  async tap(x, y) {
280
315
  this.adb("shell", "input", "tap", `${x}`, `${y}`);
281
316
  }
317
+ async longPress(x, y) {
318
+ // a long press is a swipe with no movement and a long duration
319
+ this.adb("shell", "input", "swipe", `${x}`, `${y}`, `${x}`, `${y}`, "500");
320
+ }
282
321
  async setOrientation(orientation) {
283
- const orientationValue = orientation === "portrait" ? 0 : 1;
322
+ const value = orientation === "portrait" ? 0 : 1;
284
323
  // disable auto-rotation prior to setting the orientation
285
324
  this.adb("shell", "settings", "put", "system", "accelerometer_rotation", "0");
286
- this.adb("shell", "content", "insert", "--uri", "content://settings/system", "--bind", "name:s:user_rotation", "--bind", `value:i:${orientationValue}`);
325
+ this.adb("shell", "content", "insert", "--uri", "content://settings/system", "--bind", "name:s:user_rotation", "--bind", `value:i:${value}`);
287
326
  }
288
327
  async getOrientation() {
289
328
  const rotation = this.adb("shell", "settings", "get", "system", "user_rotation").toString().trim();
package/lib/ios.js CHANGED
@@ -117,6 +117,10 @@ class IosRobot {
117
117
  const wda = await this.wda();
118
118
  await wda.tap(x, y);
119
119
  }
120
+ async longPress(x, y) {
121
+ const wda = await this.wda();
122
+ await wda.longPress(x, y);
123
+ }
120
124
  async getElementsOnScreen() {
121
125
  const wda = await this.wda();
122
126
  return await wda.getElementsOnScreen();
@@ -104,6 +104,10 @@ class Simctl {
104
104
  const wda = await this.wda();
105
105
  return wda.tap(x, y);
106
106
  }
107
+ async longPress(x, y) {
108
+ const wda = await this.wda();
109
+ return wda.longPress(x, y);
110
+ }
107
111
  async pressButton(button) {
108
112
  const wda = await this.wda();
109
113
  return wda.pressButton(button);
package/lib/server.js CHANGED
@@ -229,6 +229,14 @@ const createMcpServer = () => {
229
229
  await robot.tap(x, y);
230
230
  return `Clicked on screen at coordinates: ${x}, ${y}`;
231
231
  });
232
+ 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.", {
233
+ x: zod_1.z.number().describe("The x coordinate to long press on the screen, in pixels"),
234
+ y: zod_1.z.number().describe("The y coordinate to long press on the screen, in pixels"),
235
+ }, async ({ x, y }) => {
236
+ requireRobot();
237
+ await robot.longPress(x, y);
238
+ return `Long pressed on screen at coordinates: ${x}, ${y}`;
239
+ });
232
240
  tool("mobile_list_elements_on_screen", "List elements on screen and their coordinates, with display text or accessibility label. Do not cache this result.", {
233
241
  noParams
234
242
  }, async ({}) => {
@@ -142,6 +142,32 @@ class WebDriverAgent {
142
142
  });
143
143
  });
144
144
  }
145
+ async longPress(x, y) {
146
+ await this.withinSession(async (sessionUrl) => {
147
+ const url = `${sessionUrl}/actions`;
148
+ await fetch(url, {
149
+ method: "POST",
150
+ headers: {
151
+ "Content-Type": "application/json",
152
+ },
153
+ body: JSON.stringify({
154
+ actions: [
155
+ {
156
+ type: "pointer",
157
+ id: "finger1",
158
+ parameters: { pointerType: "touch" },
159
+ actions: [
160
+ { type: "pointerMove", duration: 0, x, y },
161
+ { type: "pointerDown", button: 0 },
162
+ { type: "pause", duration: 500 },
163
+ { type: "pointerUp", button: 0 }
164
+ ]
165
+ }
166
+ ]
167
+ }),
168
+ });
169
+ });
170
+ }
145
171
  isVisible(rect) {
146
172
  return rect.x >= 0 && rect.y >= 0;
147
173
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mobilenext/mobile-mcp",
3
- "version": "0.0.22",
3
+ "version": "0.0.24",
4
4
  "description": "Mobile MCP",
5
5
  "repository": {
6
6
  "type": "git",