@mobilenext/mobile-mcp 0.0.45 → 0.0.46

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/mobilecli.js CHANGED
@@ -19,6 +19,12 @@ class Mobilecli {
19
19
  const path = this.getPath();
20
20
  return (0, node_child_process_1.execFileSync)(path, args, { encoding: "utf8" }).toString().trim();
21
21
  }
22
+ spawnCommand(args) {
23
+ const binaryPath = this.getPath();
24
+ return (0, node_child_process_1.spawn)(binaryPath, args, {
25
+ stdio: ["ignore", "ignore", "ignore"],
26
+ });
27
+ }
22
28
  executeCommandBuffer(args) {
23
29
  const path = this.getPath();
24
30
  return (0, node_child_process_1.execFileSync)(path, args, {
package/lib/server.js CHANGED
@@ -8,6 +8,7 @@ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
8
8
  const zod_1 = require("zod");
9
9
  const node_fs_1 = __importDefault(require("node:fs"));
10
10
  const node_os_1 = __importDefault(require("node:os"));
11
+ const node_path_1 = __importDefault(require("node:path"));
11
12
  const node_crypto_1 = __importDefault(require("node:crypto"));
12
13
  const logger_1 = require("./logger");
13
14
  const android_1 = require("./android");
@@ -110,6 +111,7 @@ const createMcpServer = () => {
110
111
  }
111
112
  };
112
113
  const mobilecli = new mobilecli_1.Mobilecli();
114
+ const activeRecordings = new Map();
113
115
  posthog("launch", {}).then();
114
116
  const ensureMobilecliAvailable = () => {
115
117
  try {
@@ -154,7 +156,7 @@ const createMcpServer = () => {
154
156
  }
155
157
  throw new robot_1.ActionableError(`Device "${deviceId}" not found. Use the mobile_list_available_devices tool to see available devices.`);
156
158
  };
157
- 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.", {}, { readOnlyHint: true }, async ({}) => {
159
+ tool("mobile_list_available_devices", "List Devices", "List all available devices. This includes both physical mobile devices and mobile simulators and emulators. It returns both Android and iOS devices.", {}, { readOnlyHint: true }, async ({}) => {
158
160
  // from today onward, we must have mobilecli working
159
161
  ensureMobilecliAvailable();
160
162
  const iosManager = new ios_1.IosManager();
@@ -458,6 +460,61 @@ const createMcpServer = () => {
458
460
  const orientation = await robot.getOrientation();
459
461
  return `Current device orientation is ${orientation}`;
460
462
  });
463
+ 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
+ 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
+ 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
+ }, { destructiveHint: true }, async ({ device, output, timeLimit }) => {
468
+ getRobotFromDevice(device);
469
+ if (activeRecordings.has(device)) {
470
+ throw new robot_1.ActionableError(`Device "${device}" is already being recorded. Stop the current recording first with mobile_stop_screen_recording.`);
471
+ }
472
+ const outputPath = output || node_path_1.default.join(node_os_1.default.tmpdir(), `screen-recording-${Date.now()}.mp4`);
473
+ const args = ["screenrecord", "--device", device, "--output", outputPath, "--silent"];
474
+ if (timeLimit !== undefined) {
475
+ args.push("--time-limit", String(timeLimit));
476
+ }
477
+ const child = mobilecli.spawnCommand(args);
478
+ const cleanup = () => {
479
+ activeRecordings.delete(device);
480
+ };
481
+ child.on("error", cleanup);
482
+ child.on("exit", cleanup);
483
+ activeRecordings.set(device, {
484
+ process: child,
485
+ outputPath,
486
+ startedAt: Date.now(),
487
+ });
488
+ return `Screen recording started. Output will be saved to: ${outputPath}`;
489
+ });
490
+ tool("mobile_stop_screen_recording", "Stop Screen Recording", "Stop an active screen recording on a mobile device. Returns the file path, size, and approximate duration of the recording.", {
491
+ device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you."),
492
+ }, { destructiveHint: true }, async ({ device }) => {
493
+ const recording = activeRecordings.get(device);
494
+ if (!recording) {
495
+ throw new robot_1.ActionableError(`No active recording found for device "${device}". Start a recording first with mobile_start_screen_recording.`);
496
+ }
497
+ const { process: child, outputPath, startedAt } = recording;
498
+ activeRecordings.delete(device);
499
+ child.kill("SIGINT");
500
+ await new Promise(resolve => {
501
+ const timeout = setTimeout(() => {
502
+ child.kill("SIGKILL");
503
+ resolve();
504
+ }, 5 * 60 * 1000);
505
+ child.on("close", () => {
506
+ clearTimeout(timeout);
507
+ resolve();
508
+ });
509
+ });
510
+ const durationSeconds = Math.round((Date.now() - startedAt) / 1000);
511
+ if (!node_fs_1.default.existsSync(outputPath)) {
512
+ return `Recording stopped after ~${durationSeconds}s but the output file was not found at: ${outputPath}`;
513
+ }
514
+ const stats = node_fs_1.default.statSync(outputPath);
515
+ const fileSizeMB = (stats.size / (1024 * 1024)).toFixed(2);
516
+ return `Recording stopped. File: ${outputPath} (${fileSizeMB} MB, ~${durationSeconds}s)`;
517
+ });
461
518
  return server;
462
519
  };
463
520
  exports.createMcpServer = createMcpServer;
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.45",
4
+ "version": "0.0.46",
5
5
  "description": "Mobile MCP",
6
6
  "repository": {
7
7
  "type": "git",
@@ -34,7 +34,7 @@
34
34
  "zod-to-json-schema": "3.25.0"
35
35
  },
36
36
  "optionalDependencies": {
37
- "@mobilenext/mobilecli": "0.1.59"
37
+ "@mobilenext/mobilecli": "0.1.60"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@eslint/eslintrc": "^3.2.0",