@mobilenext/mobile-mcp 0.0.16 → 0.0.18
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/README.md +27 -10
- package/lib/android.js +4 -0
- package/lib/index.js +53 -10
- package/lib/iphone-simulator.js +5 -31
- package/lib/server.js +5 -3
- package/lib/webdriver-agent.js +2 -1
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -3,25 +3,39 @@
|
|
|
3
3
|
This is a [Model Context Protocol (MCP) server](https://github.com/modelcontextprotocol) that enables scalable mobile automation, development through a platform-agnostic interface, eliminating the need for distinct iOS or Android knowledge. You can run it on emulators, simulators, and physical devices (iOS and Android).
|
|
4
4
|
This server allows Agents and LLMs to interact with native iOS/Android applications and devices through structured accessibility snapshots or coordinate-based taps based on screenshots.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
<h4 align="center">
|
|
7
|
+
<a href="https://github.com/mobile-next/mobile-mcp">
|
|
8
|
+
<img src="https://img.shields.io/github/stars/mobile-next/mobile-mcp" alt="Mobile Next Stars" />
|
|
9
|
+
</a>
|
|
10
|
+
<a href="https://github.com/mobile-next/mobile-mcp">
|
|
11
|
+
<img src="https://img.shields.io/github/contributors/mobile-next/mobile-mcp?color=green" alt="Mobile Next Downloads" />
|
|
12
|
+
</a>
|
|
10
13
|
<a href="https://www.npmjs.com/package/@mobilenext/mobile-mcp">
|
|
11
|
-
<img src="https://img.shields.io/
|
|
14
|
+
<img src="https://img.shields.io/npm/dm/@mobilenext/mobile-mcp?logo=npm&style=flat&color=red" alt="npm">
|
|
12
15
|
</a>
|
|
13
|
-
|
|
14
|
-
<img src="https://img.shields.io/
|
|
16
|
+
<a href="https://github.com/mobile-next/mobile-mcp/releases">
|
|
17
|
+
<img src="https://img.shields.io/github/release/mobile-next/mobile-mcp">
|
|
15
18
|
</a>
|
|
19
|
+
<a href="https://github.com/mobile-next/mobile-mcp/blob/main/LICENSE">
|
|
20
|
+
<img src="https://img.shields.io/badge/license-Apache 2.0-blue.svg" alt="Mobile MCP is released under the Apache-2.0 License">
|
|
21
|
+
</a>
|
|
22
|
+
|
|
16
23
|
</p>
|
|
17
24
|
|
|
25
|
+
<h4 align="center">
|
|
26
|
+
<a href="http://mobilenexthq.com/join-slack">
|
|
27
|
+
<img src="https://img.shields.io/badge/join-Slack-blueviolet?logo=slack&style=flat" alt="Slack community channel" />
|
|
28
|
+
</a>
|
|
29
|
+
</p>
|
|
30
|
+
|
|
31
|
+
https://github.com/user-attachments/assets/c4e89c4f-cc71-4424-8184-bdbc8c638fa1
|
|
32
|
+
|
|
18
33
|
<p align="center">
|
|
19
34
|
<a href="https://github.com/mobile-next/">
|
|
20
35
|
<img alt="mobile-mcp" src="https://raw.githubusercontent.com/mobile-next/mobile-next-assets/refs/heads/main/mobile-mcp-banner.png" width="600">
|
|
21
36
|
</a>
|
|
22
37
|
</p>
|
|
23
38
|
|
|
24
|
-
|
|
25
39
|
### 🚀 Mobile MCP Roadmap: Building the Future of Mobile
|
|
26
40
|
|
|
27
41
|
Join us on our journey as we continuously enhance Mobile MCP!
|
|
@@ -64,7 +78,7 @@ More details in our [wiki page](https://github.com/mobile-next/mobile-mcp/wiki)
|
|
|
64
78
|
|
|
65
79
|
## Installation and configuration
|
|
66
80
|
|
|
67
|
-
Setup our MCP with Cursor, Claude, VS Code, Github Copilot:
|
|
81
|
+
Setup our MCP with Cline, Cursor, Claude, VS Code, Github Copilot:
|
|
68
82
|
|
|
69
83
|
```json
|
|
70
84
|
{
|
|
@@ -77,11 +91,13 @@ Setup our MCP with Cursor, Claude, VS Code, Github Copilot:
|
|
|
77
91
|
}
|
|
78
92
|
|
|
79
93
|
```
|
|
94
|
+
[Cline:](https://docs.cline.bot/mcp/configuring-mcp-servers) To setup Cline, just add the json above to your MCP settings file.
|
|
95
|
+
[More in our wiki](https://github.com/mobile-next/mobile-mcp/wiki/Cline)
|
|
80
96
|
|
|
81
97
|
[Claude Code:](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview)
|
|
82
98
|
|
|
83
99
|
```
|
|
84
|
-
claude mcp add mobile -- npx -y @mobilenext/mobile-mcp@latest
|
|
100
|
+
claude mcp add mobile -- npx -y @mobilenext/mobile-mcp@latest
|
|
85
101
|
```
|
|
86
102
|
|
|
87
103
|
[Read more in our wiki](https://github.com/mobile-next/mobile-mcp/wiki)! 🚀
|
|
@@ -143,6 +159,7 @@ via Whatsapp/Telegram/Slack to contact "Lauren Trown", thumbs up their response.
|
|
|
143
159
|
Open Zoom app, schedule a meeting titled "AI Hackathon" for tomorrow at 10 AM with a duration of 1 hour,
|
|
144
160
|
copy the invitation link, and send it via Gmail to contacts "team@example.com".
|
|
145
161
|
```
|
|
162
|
+
[More prompt examples can be found here.](https://github.com/mobile-next/mobile-mcp/wiki/Prompt-Example-repo-list)
|
|
146
163
|
|
|
147
164
|
## Prerequisites
|
|
148
165
|
|
package/lib/android.js
CHANGED
|
@@ -164,6 +164,10 @@ class AndroidRobot {
|
|
|
164
164
|
// only provide it if it's true, otherwise don't confuse llm
|
|
165
165
|
element.focused = true;
|
|
166
166
|
}
|
|
167
|
+
const resourceId = node["resource-id"];
|
|
168
|
+
if (resourceId !== null && resourceId !== "") {
|
|
169
|
+
element.identifier = resourceId;
|
|
170
|
+
}
|
|
167
171
|
if (element.rect.width > 0 && element.rect.height > 0) {
|
|
168
172
|
elements.push(element);
|
|
169
173
|
}
|
package/lib/index.js
CHANGED
|
@@ -1,17 +1,60 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
3
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const sse_js_1 = require("@modelcontextprotocol/sdk/server/sse.js");
|
|
4
8
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
5
9
|
const server_1 = require("./server");
|
|
6
10
|
const logger_1 = require("./logger");
|
|
7
|
-
|
|
8
|
-
|
|
11
|
+
const express_1 = __importDefault(require("express"));
|
|
12
|
+
const commander_1 = require("commander");
|
|
13
|
+
const startSseServer = async (port) => {
|
|
14
|
+
const app = (0, express_1.default)();
|
|
9
15
|
const server = (0, server_1.createMcpServer)();
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
let transport = null;
|
|
17
|
+
app.post("/mcp", (req, res) => {
|
|
18
|
+
if (transport) {
|
|
19
|
+
transport.handlePostMessage(req, res);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
app.get("/mcp", (req, res) => {
|
|
23
|
+
if (transport) {
|
|
24
|
+
transport.close();
|
|
25
|
+
}
|
|
26
|
+
transport = new sse_js_1.SSEServerTransport("/mcp", res);
|
|
27
|
+
server.connect(transport);
|
|
28
|
+
});
|
|
29
|
+
app.listen(port, () => {
|
|
30
|
+
(0, logger_1.error)(`mobile-mcp ${(0, server_1.getAgentVersion)()} sse server listening on http://localhost:${port}/mcp`);
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
const startStdioServer = async () => {
|
|
34
|
+
try {
|
|
35
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
36
|
+
const server = (0, server_1.createMcpServer)();
|
|
37
|
+
await server.connect(transport);
|
|
38
|
+
(0, logger_1.error)("mobile-mcp server running on stdio");
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
console.error("Fatal error in main():", err);
|
|
42
|
+
(0, logger_1.error)("Fatal error in main(): " + JSON.stringify(err.stack));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const main = async () => {
|
|
47
|
+
commander_1.program
|
|
48
|
+
.version((0, server_1.getAgentVersion)())
|
|
49
|
+
.option("--port <port>", "Start SSE server on this port")
|
|
50
|
+
.option("--stdio", "Start stdio server (default)")
|
|
51
|
+
.parse(process.argv);
|
|
52
|
+
const options = commander_1.program.opts();
|
|
53
|
+
if (options.port) {
|
|
54
|
+
await startSseServer(+options.port);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
await startStdioServer();
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
main().then();
|
package/lib/iphone-simulator.js
CHANGED
|
@@ -39,39 +39,13 @@ class Simctl {
|
|
|
39
39
|
async terminateApp(packageName) {
|
|
40
40
|
this.simctl("terminate", this.simulatorUuid, packageName);
|
|
41
41
|
}
|
|
42
|
-
parseIOSAppData(inputText) {
|
|
43
|
-
const result = [];
|
|
44
|
-
// Remove leading and trailing characters if needed
|
|
45
|
-
const cleanText = inputText.trim();
|
|
46
|
-
// Extract each app section
|
|
47
|
-
const appRegex = /"([^"]+)"\s+=\s+\{([^}]+)\};/g;
|
|
48
|
-
let appMatch;
|
|
49
|
-
while ((appMatch = appRegex.exec(cleanText)) !== null) {
|
|
50
|
-
// const bundleId = appMatch[1];
|
|
51
|
-
const appContent = appMatch[2];
|
|
52
|
-
const appInfo = {};
|
|
53
|
-
// parse simple key-value pairs
|
|
54
|
-
const keyValueRegex = /\s+(\w+)\s+=\s+([^;]+);/g;
|
|
55
|
-
let keyValueMatch;
|
|
56
|
-
while ((keyValueMatch = keyValueRegex.exec(appContent)) !== null) {
|
|
57
|
-
const key = keyValueMatch[1];
|
|
58
|
-
let value = keyValueMatch[2].trim();
|
|
59
|
-
// Handle quoted string values
|
|
60
|
-
if (value.startsWith('"') && value.endsWith('"')) {
|
|
61
|
-
value = value.substring(1, value.length - 1);
|
|
62
|
-
}
|
|
63
|
-
if (key !== "GroupContainers" && key !== "SBAppTags") {
|
|
64
|
-
appInfo[key] = value;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
result.push(appInfo);
|
|
68
|
-
}
|
|
69
|
-
return result;
|
|
70
|
-
}
|
|
71
42
|
async listApps() {
|
|
72
43
|
const text = this.simctl("listapps", this.simulatorUuid).toString();
|
|
73
|
-
const
|
|
74
|
-
|
|
44
|
+
const result = (0, child_process_1.execFileSync)("plutil", ["-convert", "json", "-o", "-", "-r", "-"], {
|
|
45
|
+
input: text,
|
|
46
|
+
});
|
|
47
|
+
const output = JSON.parse(result.toString());
|
|
48
|
+
return Object.values(output).map(app => ({
|
|
75
49
|
packageName: app.CFBundleIdentifier,
|
|
76
50
|
appName: app.CFBundleDisplayName,
|
|
77
51
|
}));
|
package/lib/server.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createMcpServer = void 0;
|
|
3
|
+
exports.createMcpServer = exports.getAgentVersion = void 0;
|
|
4
4
|
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
5
5
|
const zod_1 = require("zod");
|
|
6
6
|
const logger_1 = require("./logger");
|
|
@@ -14,6 +14,7 @@ const getAgentVersion = () => {
|
|
|
14
14
|
const json = require("../package.json");
|
|
15
15
|
return json.version;
|
|
16
16
|
};
|
|
17
|
+
exports.getAgentVersion = getAgentVersion;
|
|
17
18
|
const getLatestAgentVersion = async () => {
|
|
18
19
|
const response = await fetch("https://api.github.com/repos/mobile-next/mobile-mcp/tags?per_page=1");
|
|
19
20
|
const json = await response.json();
|
|
@@ -22,7 +23,7 @@ const getLatestAgentVersion = async () => {
|
|
|
22
23
|
const checkForLatestAgentVersion = async () => {
|
|
23
24
|
try {
|
|
24
25
|
const latestVersion = await getLatestAgentVersion();
|
|
25
|
-
const currentVersion = getAgentVersion();
|
|
26
|
+
const currentVersion = (0, exports.getAgentVersion)();
|
|
26
27
|
if (latestVersion !== currentVersion) {
|
|
27
28
|
(0, logger_1.trace)(`You are running an older version of the agent. Please update to the latest version: ${latestVersion}.`);
|
|
28
29
|
}
|
|
@@ -34,7 +35,7 @@ const checkForLatestAgentVersion = async () => {
|
|
|
34
35
|
const createMcpServer = () => {
|
|
35
36
|
const server = new mcp_js_1.McpServer({
|
|
36
37
|
name: "mobile-mcp",
|
|
37
|
-
version: getAgentVersion(),
|
|
38
|
+
version: (0, exports.getAgentVersion)(),
|
|
38
39
|
capabilities: {
|
|
39
40
|
resources: {},
|
|
40
41
|
tools: {},
|
|
@@ -159,6 +160,7 @@ const createMcpServer = () => {
|
|
|
159
160
|
label: element.label,
|
|
160
161
|
name: element.name,
|
|
161
162
|
value: element.value,
|
|
163
|
+
identifier: element.identifier,
|
|
162
164
|
coordinates: {
|
|
163
165
|
x: element.rect.x,
|
|
164
166
|
y: element.rect.y,
|
package/lib/webdriver-agent.js
CHANGED
|
@@ -130,12 +130,13 @@ class WebDriverAgent {
|
|
|
130
130
|
const acceptedTypes = ["TextField", "Button", "Switch", "Icon", "SearchField", "StaticText", "Image"];
|
|
131
131
|
if (acceptedTypes.includes(source.type)) {
|
|
132
132
|
if (source.isVisible === "1" && this.isVisible(source.rect)) {
|
|
133
|
-
if (source.label !== null || source.name !== null) {
|
|
133
|
+
if (source.label !== null || source.name !== null || source.rawIdentifier !== null) {
|
|
134
134
|
output.push({
|
|
135
135
|
type: source.type,
|
|
136
136
|
label: source.label,
|
|
137
137
|
name: source.name,
|
|
138
138
|
value: source.value,
|
|
139
|
+
identifier: source.rawIdentifier,
|
|
139
140
|
rect: {
|
|
140
141
|
x: source.rect.x,
|
|
141
142
|
y: source.rect.y,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mobilenext/mobile-mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.18",
|
|
4
4
|
"description": "Mobile MCP",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -23,6 +23,10 @@
|
|
|
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
|
+
"commander": "^14.0.0",
|
|
29
|
+
"express": "^5.1.0",
|
|
26
30
|
"fast-xml-parser": "^5.0.9",
|
|
27
31
|
"zod-to-json-schema": "^3.24.4"
|
|
28
32
|
},
|
|
@@ -40,8 +44,8 @@
|
|
|
40
44
|
"eslint-plugin-import": "^2.31.0",
|
|
41
45
|
"eslint-plugin-notice": "^1.0.0",
|
|
42
46
|
"husky": "^9.1.7",
|
|
43
|
-
"nyc": "^17.1.0",
|
|
44
47
|
"mocha": "^11.1.0",
|
|
48
|
+
"nyc": "^17.1.0",
|
|
45
49
|
"ts-node": "^10.9.2",
|
|
46
50
|
"typescript": "^5.8.2"
|
|
47
51
|
},
|