@mobilenext/mobile-mcp 0.0.20 → 0.0.22

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
@@ -37,14 +37,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.AndroidDeviceManager = exports.AndroidRobot = void 0;
40
- const path_1 = __importDefault(require("path"));
41
- const child_process_1 = require("child_process");
40
+ const node_path_1 = __importDefault(require("node:path"));
41
+ const node_child_process_1 = require("node:child_process");
42
42
  const xml = __importStar(require("fast-xml-parser"));
43
43
  const robot_1 = require("./robot");
44
44
  const getAdbPath = () => {
45
45
  let executable = "adb";
46
46
  if (process.env.ANDROID_HOME) {
47
- executable = path_1.default.join(process.env.ANDROID_HOME, "platform-tools", "adb");
47
+ executable = node_path_1.default.join(process.env.ANDROID_HOME, "platform-tools", "adb");
48
48
  }
49
49
  return executable;
50
50
  };
@@ -68,7 +68,7 @@ class AndroidRobot {
68
68
  this.deviceId = deviceId;
69
69
  }
70
70
  adb(...args) {
71
- return (0, child_process_1.execFileSync)(getAdbPath(), ["-s", this.deviceId, ...args], {
71
+ return (0, node_child_process_1.execFileSync)(getAdbPath(), ["-s", this.deviceId, ...args], {
72
72
  maxBuffer: MAX_BUFFER_SIZE,
73
73
  timeout: TIMEOUT,
74
74
  });
@@ -111,6 +111,7 @@ class AndroidRobot {
111
111
  return this.adb("shell", "pm", "list", "packages")
112
112
  .toString()
113
113
  .split("\n")
114
+ .map(line => line.trim())
114
115
  .filter(line => line.startsWith("package:"))
115
116
  .map(line => line.substring("package:".length));
116
117
  }
@@ -333,11 +334,12 @@ class AndroidDeviceManager {
333
334
  }
334
335
  getConnectedDevices() {
335
336
  try {
336
- const names = (0, child_process_1.execFileSync)(getAdbPath(), ["devices"])
337
+ const names = (0, node_child_process_1.execFileSync)(getAdbPath(), ["devices"])
337
338
  .toString()
338
339
  .split("\n")
340
+ .map(line => line.trim())
341
+ .filter(line => line !== "")
339
342
  .filter(line => !line.startsWith("List of devices attached"))
340
- .filter(line => line.trim() !== "")
341
343
  .map(line => line.split("\t")[0]);
342
344
  return names.map(name => ({
343
345
  deviceId: name,
package/lib/ios.js CHANGED
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.IosManager = exports.IosRobot = void 0;
4
- const child_process_1 = require("child_process");
5
- const net_1 = require("net");
4
+ const node_net_1 = require("node:net");
5
+ const node_child_process_1 = require("node:child_process");
6
6
  const webdriver_agent_1 = require("./webdriver-agent");
7
7
  const robot_1 = require("./robot");
8
8
  const WDA_PORT = 8100;
@@ -21,7 +21,7 @@ class IosRobot {
21
21
  }
22
22
  isListeningOnPort(port) {
23
23
  return new Promise((resolve, reject) => {
24
- const client = new net_1.Socket();
24
+ const client = new node_net_1.Socket();
25
25
  client.connect(port, "localhost", () => {
26
26
  client.destroy();
27
27
  resolve(true);
@@ -56,7 +56,7 @@ class IosRobot {
56
56
  return wda;
57
57
  }
58
58
  async ios(...args) {
59
- return (0, child_process_1.execFileSync)(getGoIosPath(), ["--udid", this.deviceId, ...args], {}).toString();
59
+ return (0, node_child_process_1.execFileSync)(getGoIosPath(), ["--udid", this.deviceId, ...args], {}).toString();
60
60
  }
61
61
  async getIosVersion() {
62
62
  const output = await this.ios("info");
@@ -144,9 +144,9 @@ class IosRobot {
144
144
  }
145
145
  exports.IosRobot = IosRobot;
146
146
  class IosManager {
147
- async isGoIosInstalled() {
147
+ isGoIosInstalled() {
148
148
  try {
149
- const output = (0, child_process_1.execFileSync)(getGoIosPath(), ["version"], { stdio: ["pipe", "pipe", "ignore"] }).toString();
149
+ const output = (0, node_child_process_1.execFileSync)(getGoIosPath(), ["version"], { stdio: ["pipe", "pipe", "ignore"] }).toString();
150
150
  const json = JSON.parse(output);
151
151
  return json.version !== undefined && (json.version.startsWith("v") || json.version === "local-build");
152
152
  }
@@ -155,7 +155,7 @@ class IosManager {
155
155
  }
156
156
  }
157
157
  getDeviceName(deviceId) {
158
- const output = (0, child_process_1.execFileSync)(getGoIosPath(), ["info", "--udid", deviceId]).toString();
158
+ const output = (0, node_child_process_1.execFileSync)(getGoIosPath(), ["info", "--udid", deviceId]).toString();
159
159
  const json = JSON.parse(output);
160
160
  return json.DeviceName;
161
161
  }
@@ -164,7 +164,7 @@ class IosManager {
164
164
  console.error("go-ios is not installed, no physical iOS devices can be detected");
165
165
  return [];
166
166
  }
167
- const output = (0, child_process_1.execFileSync)(getGoIosPath(), ["list"]).toString();
167
+ const output = (0, node_child_process_1.execFileSync)(getGoIosPath(), ["list"]).toString();
168
168
  const json = JSON.parse(output);
169
169
  const devices = json.deviceList.map(device => ({
170
170
  deviceId: device,
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SimctlManager = exports.Simctl = void 0;
4
- const child_process_1 = require("child_process");
4
+ const node_child_process_1 = require("node:child_process");
5
+ const logger_1 = require("./logger");
5
6
  const webdriver_agent_1 = require("./webdriver-agent");
6
7
  const robot_1 = require("./robot");
7
8
  const TIMEOUT = 30000;
@@ -12,15 +13,46 @@ class Simctl {
12
13
  constructor(simulatorUuid) {
13
14
  this.simulatorUuid = simulatorUuid;
14
15
  }
16
+ async isWdaInstalled() {
17
+ const apps = await this.listApps();
18
+ return apps.map(app => app.packageName).includes("com.facebook.WebDriverAgentRunner.xctrunner");
19
+ }
20
+ async startWda() {
21
+ if (!(await this.isWdaInstalled())) {
22
+ // wda is not even installed, won't attempt to start it
23
+ return;
24
+ }
25
+ (0, logger_1.trace)("Starting WebDriverAgent");
26
+ const webdriverPackageName = "com.facebook.WebDriverAgentRunner.xctrunner";
27
+ this.simctl("launch", this.simulatorUuid, webdriverPackageName);
28
+ // now we wait for wda to have a successful status
29
+ const wda = new webdriver_agent_1.WebDriverAgent("localhost", WDA_PORT);
30
+ // wait up to 10 seconds for wda to start
31
+ const timeout = +new Date() + 10 * 1000;
32
+ while (+new Date() < timeout) {
33
+ // cross fingers and see if wda is already running
34
+ if (await wda.isRunning()) {
35
+ (0, logger_1.trace)("WebDriverAgent is now running");
36
+ return;
37
+ }
38
+ // wait 100ms before trying again
39
+ await new Promise(resolve => setTimeout(resolve, 100));
40
+ }
41
+ (0, logger_1.trace)("Could not start WebDriverAgent in time, giving up");
42
+ }
15
43
  async wda() {
16
44
  const wda = new webdriver_agent_1.WebDriverAgent("localhost", WDA_PORT);
17
45
  if (!(await wda.isRunning())) {
18
- throw new robot_1.ActionableError("WebDriverAgent is not running on simulator, please see https://github.com/mobile-next/mobile-mcp/wiki/");
46
+ await this.startWda();
47
+ if (!(await wda.isRunning())) {
48
+ throw new robot_1.ActionableError("WebDriverAgent is not running on simulator, please see https://github.com/mobile-next/mobile-mcp/wiki/");
49
+ }
50
+ // was successfully started
19
51
  }
20
52
  return wda;
21
53
  }
22
54
  simctl(...args) {
23
- return (0, child_process_1.execFileSync)("xcrun", ["simctl", ...args], {
55
+ return (0, node_child_process_1.execFileSync)("xcrun", ["simctl", ...args], {
24
56
  timeout: TIMEOUT,
25
57
  maxBuffer: MAX_BUFFER_SIZE,
26
58
  });
@@ -43,7 +75,7 @@ class Simctl {
43
75
  }
44
76
  async listApps() {
45
77
  const text = this.simctl("listapps", this.simulatorUuid).toString();
46
- const result = (0, child_process_1.execFileSync)("plutil", ["-convert", "json", "-o", "-", "-r", "-"], {
78
+ const result = (0, node_child_process_1.execFileSync)("plutil", ["-convert", "json", "-o", "-", "-r", "-"], {
47
79
  input: text,
48
80
  });
49
81
  const output = JSON.parse(result.toString());
@@ -98,7 +130,7 @@ class SimctlManager {
98
130
  return [];
99
131
  }
100
132
  try {
101
- const text = (0, child_process_1.execFileSync)("xcrun", ["simctl", "list", "devices", "-j"]).toString();
133
+ const text = (0, node_child_process_1.execFileSync)("xcrun", ["simctl", "list", "devices", "-j"]).toString();
102
134
  const json = JSON.parse(text);
103
135
  return Object.values(json.devices).flatMap(device => {
104
136
  return device.map(d => {
package/lib/logger.js CHANGED
@@ -1,14 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.error = exports.trace = void 0;
4
- const fs_1 = require("fs");
4
+ const node_fs_1 = require("node:fs");
5
5
  const writeLog = (message) => {
6
6
  if (process.env.LOG_FILE) {
7
7
  const logfile = process.env.LOG_FILE;
8
8
  const timestamp = new Date().toISOString();
9
9
  const levelStr = "INFO";
10
10
  const logMessage = `[${timestamp}] ${levelStr} ${message}`;
11
- (0, fs_1.appendFileSync)(logfile, logMessage + "\n");
11
+ (0, node_fs_1.appendFileSync)(logfile, logMessage + "\n");
12
12
  }
13
13
  console.error(message);
14
14
  };
package/lib/server.js CHANGED
@@ -7,6 +7,7 @@ exports.createMcpServer = exports.getAgentVersion = void 0;
7
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
+ const node_os_1 = __importDefault(require("node:os"));
10
11
  const node_crypto_1 = __importDefault(require("node:crypto"));
11
12
  const logger_1 = require("./logger");
12
13
  const android_1 = require("./android");
@@ -54,6 +55,7 @@ const createMcpServer = () => {
54
55
  (0, logger_1.trace)(`Invoking ${name} with args: ${JSON.stringify(args)}`);
55
56
  const response = await cb(args);
56
57
  (0, logger_1.trace)(`=> ${response}`);
58
+ posthog("tool_invoked", {}).then();
57
59
  return {
58
60
  content: [{ type: "text", text: response }],
59
61
  };
@@ -76,27 +78,39 @@ const createMcpServer = () => {
76
78
  };
77
79
  server.tool(name, description, paramsSchema, args => wrappedCb(args));
78
80
  };
79
- const posthog = (event, properties) => {
80
- const url = "https://us.i.posthog.com/i/v0/e/";
81
- const api_key = "phc_KHRTZmkDsU7A8EbydEK8s4lJpPoTDyyBhSlwer694cS";
82
- const distinct_id = node_crypto_1.default.createHash("sha256").update(process.execPath).digest("hex");
83
- fetch(url, {
84
- method: "POST",
85
- headers: {
86
- "Content-Type": "application/json"
87
- },
88
- body: JSON.stringify({
89
- api_key,
90
- event,
91
- properties,
92
- distinct_id,
93
- })
94
- }).then().catch();
81
+ const posthog = async (event, properties) => {
82
+ try {
83
+ const url = "https://us.i.posthog.com/i/v0/e/";
84
+ const api_key = "phc_KHRTZmkDsU7A8EbydEK8s4lJpPoTDyyBhSlwer694cS";
85
+ const name = node_os_1.default.hostname() + process.execPath;
86
+ const distinct_id = node_crypto_1.default.createHash("sha256").update(name).digest("hex");
87
+ const systemProps = {
88
+ Platform: node_os_1.default.platform(),
89
+ Product: "mobile-mcp",
90
+ Version: (0, exports.getAgentVersion)(),
91
+ NodeVersion: process.version,
92
+ };
93
+ await fetch(url, {
94
+ method: "POST",
95
+ headers: {
96
+ "Content-Type": "application/json"
97
+ },
98
+ body: JSON.stringify({
99
+ api_key,
100
+ event,
101
+ properties: {
102
+ ...systemProps,
103
+ ...properties,
104
+ },
105
+ distinct_id,
106
+ })
107
+ });
108
+ }
109
+ catch (err) {
110
+ // ignore
111
+ }
95
112
  };
96
- posthog("launch", {
97
- Product: "mobile-mcp",
98
- Version: (0, exports.getAgentVersion)(),
99
- });
113
+ posthog("launch", {}).then();
100
114
  let robot;
101
115
  const simulatorManager = new iphone_simulator_1.SimctlManager();
102
116
  const requireRobot = () => {
@@ -13,10 +13,11 @@ class WebDriverAgent {
13
13
  const url = `http://${this.host}:${this.port}/status`;
14
14
  try {
15
15
  const response = await fetch(url);
16
- return response.status === 200;
16
+ const json = await response.json();
17
+ return response.status === 200 && json.value?.ready === true;
17
18
  }
18
19
  catch (error) {
19
- console.error(`Failed to connect to WebDriverAgent: ${error}`);
20
+ // console.error(`Failed to connect to WebDriverAgent: ${error}`);
20
21
  return false;
21
22
  }
22
23
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mobilenext/mobile-mcp",
3
- "version": "0.0.20",
3
+ "version": "0.0.22",
4
4
  "description": "Mobile MCP",
5
5
  "repository": {
6
6
  "type": "git",
@@ -23,8 +23,6 @@
23
23
  ],
24
24
  "dependencies": {
25
25
  "@modelcontextprotocol/sdk": "^1.6.1",
26
- "@types/commander": "^2.12.0",
27
- "@types/express": "^5.0.3",
28
26
  "commander": "^14.0.0",
29
27
  "express": "^5.1.0",
30
28
  "fast-xml-parser": "^5.0.9",
@@ -34,6 +32,8 @@
34
32
  "@eslint/eslintrc": "^3.2.0",
35
33
  "@eslint/js": "^9.19.0",
36
34
  "@stylistic/eslint-plugin": "^3.0.1",
35
+ "@types/commander": "^2.12.0",
36
+ "@types/express": "^5.0.3",
37
37
  "@types/mocha": "^10.0.10",
38
38
  "@types/node": "^22.13.10",
39
39
  "@typescript-eslint/eslint-plugin": "^8.28.0",