@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 +8 -6
- package/lib/ios.js +8 -8
- package/lib/iphone-simulator.js +37 -5
- package/lib/logger.js +2 -2
- package/lib/server.js +34 -20
- package/lib/webdriver-agent.js +3 -2
- package/package.json +3 -3
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
|
|
41
|
-
const
|
|
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 =
|
|
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,
|
|
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,
|
|
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
|
|
5
|
-
const
|
|
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
|
|
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,
|
|
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
|
-
|
|
147
|
+
isGoIosInstalled() {
|
|
148
148
|
try {
|
|
149
|
-
const output = (0,
|
|
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,
|
|
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,
|
|
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,
|
package/lib/iphone-simulator.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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 = () => {
|
package/lib/webdriver-agent.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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",
|