@mobilenext/mobile-mcp 0.0.17 → 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 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
- https://github.com/user-attachments/assets/c4e89c4f-cc71-4424-8184-bdbc8c638fa1
7
-
8
-
9
- <p align="center">
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/badge/npm-@mobilenext/mobile--mcp-red" alt="npm">
14
+ <img src="https://img.shields.io/npm/dm/@mobilenext/mobile-mcp?logo=npm&style=flat&color=red" alt="npm">
12
15
  </a>
13
- <a href="https://github.com/mobile-next/mobile-mcp">
14
- <img src="https://img.shields.io/badge/github-repo-black" alt="GitHub repo">
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
- async function main() {
8
- const transport = new stdio_js_1.StdioServerTransport();
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
- await server.connect(transport);
11
- (0, logger_1.error)("mobile-mcp server running on stdio");
12
- }
13
- main().catch(err => {
14
- console.error("Fatal error in main():", err);
15
- (0, logger_1.error)("Fatal error in main(): " + JSON.stringify(err.stack));
16
- process.exit(1);
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();
@@ -39,75 +39,13 @@ class Simctl {
39
39
  async terminateApp(packageName) {
40
40
  this.simctl("terminate", this.simulatorUuid, packageName);
41
41
  }
42
- static parseIOSAppData(inputText) {
43
- const result = [];
44
- let ParseState;
45
- (function (ParseState) {
46
- ParseState[ParseState["LOOKING_FOR_APP"] = 0] = "LOOKING_FOR_APP";
47
- ParseState[ParseState["IN_APP"] = 1] = "IN_APP";
48
- ParseState[ParseState["IN_PROPERTY"] = 2] = "IN_PROPERTY";
49
- })(ParseState || (ParseState = {}));
50
- let state = ParseState.LOOKING_FOR_APP;
51
- let currentApp = {};
52
- let appIdentifier = "";
53
- const lines = inputText.split("\n");
54
- for (let line of lines) {
55
- line = line.trim();
56
- if (line === "") {
57
- continue;
58
- }
59
- switch (state) {
60
- case ParseState.LOOKING_FOR_APP:
61
- // look for app identifier pattern: "com.example.app" = {
62
- const appMatch = line.match(/^"?([^"=]+)"?\s*=\s*\{/);
63
- if (appMatch) {
64
- appIdentifier = appMatch[1].trim();
65
- currentApp = {
66
- CFBundleIdentifier: appIdentifier,
67
- };
68
- state = ParseState.IN_APP;
69
- }
70
- break;
71
- case ParseState.IN_APP:
72
- if (line === "};") {
73
- result.push(currentApp);
74
- currentApp = {};
75
- state = ParseState.LOOKING_FOR_APP;
76
- }
77
- else {
78
- // look for property: PropertyName = Value;
79
- const propertyMatch = line.match(/^([^=]+)\s*=\s*(.+?);\s*$/);
80
- if (propertyMatch) {
81
- const propName = propertyMatch[1].trim();
82
- let propValue = propertyMatch[2].trim();
83
- // remove quotes if present (they're optional)
84
- if (propValue.startsWith('"') && propValue.endsWith('"')) {
85
- propValue = propValue.substring(1, propValue.length - 1);
86
- }
87
- // add property to current app
88
- currentApp[propName] = propValue;
89
- }
90
- else if (line.endsWith("{")) {
91
- // nested property like GroupContainers = {
92
- state = ParseState.IN_PROPERTY;
93
- }
94
- }
95
- break;
96
- case ParseState.IN_PROPERTY:
97
- if (line === "};") {
98
- // end of nested property
99
- state = ParseState.IN_APP;
100
- }
101
- // skip content of nested properties, we don't care of those right now
102
- break;
103
- }
104
- }
105
- return result;
106
- }
107
42
  async listApps() {
108
43
  const text = this.simctl("listapps", this.simulatorUuid).toString();
109
- const apps = Simctl.parseIOSAppData(text);
110
- return apps.map(app => ({
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 => ({
111
49
  packageName: app.CFBundleIdentifier,
112
50
  appName: app.CFBundleDisplayName,
113
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,
@@ -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.17",
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
  },