@mobilenext/mobile-mcp 0.0.50 → 0.0.52
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 +49 -0
- package/lib/index.js +52 -7
- package/lib/mobilecli.js +2 -2
- package/lib/server.js +3 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -350,6 +350,31 @@ Or add the standard config under `mcpServers` in your settings as shown above.
|
|
|
350
350
|
|
|
351
351
|
[Read more in our wiki](https://github.com/mobile-next/mobile-mcp/wiki)! 🚀
|
|
352
352
|
|
|
353
|
+
### SSE Server Mode
|
|
354
|
+
|
|
355
|
+
By default, Mobile MCP runs over stdio. To start an SSE server instead, use the `--listen` flag:
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
npx @mobilenext/mobile-mcp@latest --listen 3000
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
This binds to `localhost:3000`. To bind to a specific interface:
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
npx @mobilenext/mobile-mcp@latest --listen 0.0.0.0:3000
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Then configure your MCP client to connect to `http://<host>:3000/mcp`.
|
|
368
|
+
|
|
369
|
+
#### Authorization
|
|
370
|
+
|
|
371
|
+
To require Bearer token authorization on the SSE server, set the `MOBILEMCP_AUTH` environment variable:
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
MOBILEMCP_AUTH=my-secret-token npx @mobilenext/mobile-mcp@latest --listen 3000
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
When set, all requests must include the header `Authorization: Bearer my-secret-token`.
|
|
353
378
|
|
|
354
379
|
### 🛠️ How to Use 📝
|
|
355
380
|
|
|
@@ -435,6 +460,30 @@ When launched, Mobile MCP can connect to:
|
|
|
435
460
|
|
|
436
461
|
Make sure you have your mobile platform SDKs (Xcode, Android SDK) installed and configured properly before running Mobile Next Mobile MCP.
|
|
437
462
|
|
|
463
|
+
### Telemetry
|
|
464
|
+
|
|
465
|
+
Mobile MCP collects anonymous usage telemetry via PostHog. To disable it, set the `MOBILEMCP_DISABLE_TELEMETRY` environment variable:
|
|
466
|
+
|
|
467
|
+
```bash
|
|
468
|
+
MOBILEMCP_DISABLE_TELEMETRY=1 npx @mobilenext/mobile-mcp@latest
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
For json configurations:
|
|
472
|
+
|
|
473
|
+
```json
|
|
474
|
+
{
|
|
475
|
+
"mcpServers": {
|
|
476
|
+
"mobile-mcp": {
|
|
477
|
+
"command": "npx",
|
|
478
|
+
"args": ["-y", "@mobilenext/mobile-mcp@latest"],
|
|
479
|
+
"env": {
|
|
480
|
+
"MOBILEMCP_DISABLE_TELEMETRY": "1"
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
438
487
|
### Running in "headless" mode on Simulators/Emulators
|
|
439
488
|
|
|
440
489
|
When you do not have a real device connected to your machine, you can run Mobile MCP with an emulator or simulator in the background.
|
package/lib/index.js
CHANGED
|
@@ -10,9 +10,34 @@ const server_1 = require("./server");
|
|
|
10
10
|
const logger_1 = require("./logger");
|
|
11
11
|
const express_1 = __importDefault(require("express"));
|
|
12
12
|
const commander_1 = require("commander");
|
|
13
|
-
const startSseServer = async (port) => {
|
|
13
|
+
const startSseServer = async (host, port) => {
|
|
14
14
|
const app = (0, express_1.default)();
|
|
15
15
|
const server = (0, server_1.createMcpServer)();
|
|
16
|
+
const authToken = process.env.MOBILEMCP_AUTH;
|
|
17
|
+
if (!authToken) {
|
|
18
|
+
(0, logger_1.error)("WARNING: MOBILEMCP_AUTH is not set. The SSE server will accept unauthenticated connections. Set MOBILEMCP_AUTH to require Bearer token authentication.");
|
|
19
|
+
}
|
|
20
|
+
if (authToken) {
|
|
21
|
+
app.use((req, res, next) => {
|
|
22
|
+
if (req.headers.authorization !== `Bearer ${authToken}`) {
|
|
23
|
+
res.status(401).json({ error: "Unauthorized" });
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
next();
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
// Block cross-origin requests — MCP clients are not browsers
|
|
30
|
+
app.use((req, res, next) => {
|
|
31
|
+
if (req.headers.origin) {
|
|
32
|
+
res.status(403).json({ error: "Cross-origin requests are not allowed" });
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (req.method === "OPTIONS") {
|
|
36
|
+
res.status(403).end();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
next();
|
|
40
|
+
});
|
|
16
41
|
let transport = null;
|
|
17
42
|
app.post("/mcp", (req, res) => {
|
|
18
43
|
if (transport) {
|
|
@@ -21,13 +46,17 @@ const startSseServer = async (port) => {
|
|
|
21
46
|
});
|
|
22
47
|
app.get("/mcp", (req, res) => {
|
|
23
48
|
if (transport) {
|
|
24
|
-
|
|
49
|
+
res.status(409).json({ error: "Another client is already connected. Disconnect the existing client first." });
|
|
50
|
+
return;
|
|
25
51
|
}
|
|
26
52
|
transport = new sse_js_1.SSEServerTransport("/mcp", res);
|
|
53
|
+
transport.onclose = () => {
|
|
54
|
+
transport = null;
|
|
55
|
+
};
|
|
27
56
|
server.connect(transport);
|
|
28
57
|
});
|
|
29
|
-
app.listen(port, () => {
|
|
30
|
-
(0, logger_1.error)(`mobile-mcp ${(0, server_1.getAgentVersion)()} sse server listening on http
|
|
58
|
+
app.listen(port, host, () => {
|
|
59
|
+
(0, logger_1.error)(`mobile-mcp ${(0, server_1.getAgentVersion)()} sse server listening on http://${host}:${port}/mcp`);
|
|
31
60
|
});
|
|
32
61
|
};
|
|
33
62
|
const startStdioServer = async () => {
|
|
@@ -46,12 +75,28 @@ const startStdioServer = async () => {
|
|
|
46
75
|
const main = async () => {
|
|
47
76
|
commander_1.program
|
|
48
77
|
.version((0, server_1.getAgentVersion)())
|
|
49
|
-
.option("--
|
|
78
|
+
.option("--listen <listen>", "Start SSE server on [host:]port")
|
|
50
79
|
.option("--stdio", "Start stdio server (default)")
|
|
51
80
|
.parse(process.argv);
|
|
52
81
|
const options = commander_1.program.opts();
|
|
53
|
-
if (options.
|
|
54
|
-
|
|
82
|
+
if (options.listen) {
|
|
83
|
+
const listen = options.listen.trim();
|
|
84
|
+
const lastColon = listen.lastIndexOf(":");
|
|
85
|
+
let host = "localhost";
|
|
86
|
+
let rawPort;
|
|
87
|
+
if (lastColon > 0) {
|
|
88
|
+
host = listen.substring(0, lastColon);
|
|
89
|
+
rawPort = listen.substring(lastColon + 1);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
rawPort = listen;
|
|
93
|
+
}
|
|
94
|
+
const port = Number.parseInt(rawPort, 10);
|
|
95
|
+
if (!host || !rawPort || !Number.isInteger(port) || port < 1 || port > 65535) {
|
|
96
|
+
(0, logger_1.error)(`Invalid --listen value "${listen}". Expected [host:]port with port 1-65535.`);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
await startSseServer(host, port);
|
|
55
100
|
}
|
|
56
101
|
else {
|
|
57
102
|
await startStdioServer();
|
package/lib/mobilecli.js
CHANGED
|
@@ -51,7 +51,7 @@ class Mobilecli {
|
|
|
51
51
|
// We're inside node_modules, go to the last node_modules in the path
|
|
52
52
|
const nodeModulesParts = pathParts.slice(0, lastNodeModulesIndex + 1);
|
|
53
53
|
const lastNodeModulesPath = nodeModulesParts.join(node_path_1.sep);
|
|
54
|
-
const mobilecliPath = (0, node_path_1.join)(lastNodeModulesPath, "
|
|
54
|
+
const mobilecliPath = (0, node_path_1.join)(lastNodeModulesPath, "mobilecli", "bin", binaryName);
|
|
55
55
|
if ((0, node_fs_1.existsSync)(mobilecliPath)) {
|
|
56
56
|
return mobilecliPath;
|
|
57
57
|
}
|
|
@@ -59,7 +59,7 @@ class Mobilecli {
|
|
|
59
59
|
// Not in node_modules, look one directory up from current script
|
|
60
60
|
const scriptDir = (0, node_path_1.dirname)(__filename);
|
|
61
61
|
const parentDir = (0, node_path_1.dirname)(scriptDir);
|
|
62
|
-
const mobilecliPath = (0, node_path_1.join)(parentDir, "node_modules", "
|
|
62
|
+
const mobilecliPath = (0, node_path_1.join)(parentDir, "node_modules", "mobilecli", "bin", binaryName);
|
|
63
63
|
if ((0, node_fs_1.existsSync)(mobilecliPath)) {
|
|
64
64
|
return mobilecliPath;
|
|
65
65
|
}
|
package/lib/server.js
CHANGED
|
@@ -78,6 +78,9 @@ const createMcpServer = () => {
|
|
|
78
78
|
}));
|
|
79
79
|
};
|
|
80
80
|
const posthog = async (event, properties) => {
|
|
81
|
+
if (process.env.MOBILEMCP_DISABLE_TELEMETRY) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
81
84
|
try {
|
|
82
85
|
const url = "https://us.i.posthog.com/i/v0/e/";
|
|
83
86
|
const api_key = "phc_KHRTZmkDsU7A8EbydEK8s4lJpPoTDyyBhSlwer694cS";
|
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.
|
|
4
|
+
"version": "0.0.52",
|
|
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
|
-
"
|
|
37
|
+
"mobilecli": "0.2.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@eslint/eslintrc": "^3.2.0",
|