mcp-maestro-mobile-ai 1.1.0 → 1.1.1

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/CHANGELOG.md CHANGED
@@ -23,6 +23,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
23
23
  - Test results and screenshots now stored in `~/.maestro-mcp/output/`
24
24
 
25
25
  ### Added
26
+ - **Automatic Prerequisites Check**:
27
+ - Runs automatically after `npm install`
28
+ - Checks for Node.js 18+, Java 17+, Maestro CLI, Android SDK
29
+ - Shows clear error messages with installation hints
30
+ - Manual check available via `npm run check`
31
+ - **Runtime Validation**: Server validates prerequisites on startup and exits gracefully if critical deps missing
26
32
  - **App Context Training System**: New tools to teach the AI about your app's UI
27
33
  - `register_elements` - Register testIDs, accessibilityLabels for app elements
28
34
  - `register_screen` - Define screen structures and available actions
package/docs/MCP_SETUP.md CHANGED
@@ -1,18 +1,113 @@
1
1
  # MCP Server Setup Guide
2
2
 
3
- ## Setting up Maestro MCP Server with AI Clients
3
+ ## Setting up MCP Maestro Mobile AI with AI Clients
4
4
 
5
- This guide explains how to configure the Maestro MCP Server with different AI clients.
5
+ This guide explains how to configure the MCP Maestro Mobile AI server with different AI clients.
6
6
 
7
7
  ---
8
8
 
9
9
  ## Prerequisites
10
10
 
11
- 1. **Node.js 18+** installed
12
- 2. **Java 17+** installed
13
- 3. **Maestro CLI** installed
14
- 4. **Android Emulator** running
15
- 5. Dependencies installed: `npm install`
11
+ ### Required Dependencies
12
+
13
+ | Prerequisite | Version | Installation |
14
+ | --------------- | ------- | ------------------------------------- |
15
+ | **Node.js** | 18+ | [nodejs.org](https://nodejs.org/) |
16
+ | **Java** | 17+ | [adoptium.net](https://adoptium.net/) |
17
+ | **Maestro CLI** | Latest | See below |
18
+ | **Android SDK** | Any | Android Studio or standalone |
19
+
20
+ ### Install Maestro CLI
21
+
22
+ **macOS / Linux:**
23
+
24
+ ```bash
25
+ curl -Ls https://get.maestro.mobile.dev | bash
26
+ ```
27
+
28
+ **Windows (PowerShell):**
29
+
30
+ ```powershell
31
+ iwr https://get.maestro.mobile.dev | iex
32
+ ```
33
+
34
+ ### Automatic Prerequisites Check ✨
35
+
36
+ When you install the package, it **automatically checks** all prerequisites:
37
+
38
+ ```
39
+ npm install -g mcp-maestro-mobile-ai
40
+
41
+ ╔════════════════════════════════════════════════════════╗
42
+ ║ MCP Maestro Mobile AI - Prerequisites ║
43
+ ╚════════════════════════════════════════════════════════╝
44
+
45
+ ✅ Node.js v20.x
46
+ ✅ Java 17
47
+ ✅ Maestro 2.0.10
48
+ ✅ Android SDK configured
49
+
50
+ ✅ All prerequisites satisfied!
51
+ ```
52
+
53
+ If any prerequisite is missing, you'll see:
54
+
55
+ ```
56
+ ❌ Java not found
57
+ ❌ Maestro CLI not found
58
+
59
+ ⚠️ Some required prerequisites are missing.
60
+
61
+ 💡 Installation hints:
62
+ → Install Java 17+ from https://adoptium.net/
63
+ → Install Maestro: curl -Ls https://get.maestro.mobile.dev | bash
64
+ ```
65
+
66
+ ### Manual Check
67
+
68
+ You can manually check prerequisites anytime:
69
+
70
+ ```bash
71
+ # If installed globally
72
+ npm run check
73
+
74
+ # Or run directly
75
+ npx mcp-maestro-mobile-ai check
76
+ ```
77
+
78
+ ### Runtime Validation
79
+
80
+ The server also validates prerequisites **when it starts**. If Java or Maestro is missing, the server will:
81
+
82
+ 1. Show clear error messages
83
+ 2. Provide installation hints
84
+ 3. Exit with code 2 (infrastructure error)
85
+
86
+ ---
87
+
88
+ ## Installation Options
89
+
90
+ ### Option 1: NPX (Recommended - No Install)
91
+
92
+ ```bash
93
+ npx mcp-maestro-mobile-ai
94
+ ```
95
+
96
+ ### Option 2: Global Install
97
+
98
+ ```bash
99
+ npm install -g mcp-maestro-mobile-ai
100
+ maestro-mcp
101
+ ```
102
+
103
+ ### Option 3: Clone from GitHub
104
+
105
+ ```bash
106
+ git clone https://github.com/krunal-mahera/mcp-maestro-mobile-ai.git
107
+ cd mcp-maestro-mobile-ai
108
+ npm install
109
+ npm start
110
+ ```
16
111
 
17
112
  ---
18
113
 
@@ -21,29 +116,62 @@ This guide explains how to configure the Maestro MCP Server with different AI cl
21
116
  ### Step 1: Find your Cursor MCP config file
22
117
 
23
118
  The config file is located at:
119
+
24
120
  - **Windows**: `%USERPROFILE%\.cursor\mcp.json`
25
121
  - **macOS**: `~/.cursor/mcp.json`
26
122
  - **Linux**: `~/.cursor/mcp.json`
27
123
 
28
124
  ### Step 2: Add the Maestro server
29
125
 
30
- Edit the `mcp.json` file and add:
126
+ **If installed via NPM (global):**
31
127
 
32
128
  ```json
33
129
  {
34
130
  "mcpServers": {
35
131
  "maestro": {
36
- "command": "node",
37
- "args": ["C:/path/to/ai-automation-mobile/src/mcp-server/index.js"],
132
+ "command": "maestro-mcp",
133
+ "env": {
134
+ "APP_ID": "com.your.app.package",
135
+ "ANDROID_HOME": "C:/Users/YourName/AppData/Local/Android/Sdk"
136
+ }
137
+ }
138
+ }
139
+ }
140
+ ```
141
+
142
+ **If using NPX:**
143
+
144
+ ```json
145
+ {
146
+ "mcpServers": {
147
+ "maestro": {
148
+ "command": "npx",
149
+ "args": ["mcp-maestro-mobile-ai"],
38
150
  "env": {
39
- "APP_ID": "com.google.android.youtube"
151
+ "APP_ID": "com.your.app.package",
152
+ "ANDROID_HOME": "C:/Users/YourName/AppData/Local/Android/Sdk"
40
153
  }
41
154
  }
42
155
  }
43
156
  }
44
157
  ```
45
158
 
46
- **Important:** Replace the path with your actual project path.
159
+ **If cloned from GitHub:**
160
+
161
+ ```json
162
+ {
163
+ "mcpServers": {
164
+ "maestro": {
165
+ "command": "node",
166
+ "args": ["C:/path/to/mcp-maestro-mobile-ai/src/mcp-server/index.js"],
167
+ "env": {
168
+ "APP_ID": "com.your.app.package",
169
+ "ANDROID_HOME": "C:/Users/YourName/AppData/Local/Android/Sdk"
170
+ }
171
+ }
172
+ }
173
+ }
174
+ ```
47
175
 
48
176
  ### Step 3: Restart Cursor
49
177
 
@@ -52,6 +180,7 @@ Close and reopen Cursor for the changes to take effect.
52
180
  ### Step 4: Verify
53
181
 
54
182
  In Cursor's AI chat, type:
183
+
55
184
  ```
56
185
  List the available MCP tools
57
186
  ```
@@ -69,8 +198,9 @@ If there's an MCP extension for VS Code Copilot, configure it similarly to Curso
69
198
  ### Option 2: Run Server Manually
70
199
 
71
200
  1. Start the MCP server:
201
+
72
202
  ```bash
73
- cd C:\Krunal\Practice\ai-automation-mobile
203
+ cd path/to/mcp-maestro-mobile-ai
74
204
  npm start
75
205
  ```
76
206
 
@@ -78,103 +208,178 @@ If there's an MCP extension for VS Code Copilot, configure it similarly to Curso
78
208
 
79
209
  ---
80
210
 
211
+ ## Claude Desktop Setup
212
+
213
+ Edit `~/.claude/claude_desktop_config.json` (macOS/Linux) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
214
+
215
+ ```json
216
+ {
217
+ "mcpServers": {
218
+ "maestro": {
219
+ "command": "npx",
220
+ "args": ["mcp-maestro-mobile-ai"],
221
+ "env": {
222
+ "APP_ID": "com.your.app.package"
223
+ }
224
+ }
225
+ }
226
+ }
227
+ ```
228
+
229
+ ---
230
+
81
231
  ## Testing the Setup
82
232
 
83
- ### 1. Check Configuration
233
+ ### 1. Check Prerequisites
234
+
235
+ Ask the AI:
236
+
237
+ ```
238
+ Check if maestro device is connected
239
+ ```
240
+
241
+ ### 2. Check Configuration
84
242
 
85
243
  Ask the AI:
244
+
86
245
  ```
87
246
  Get the app configuration
88
247
  ```
89
248
 
90
249
  Expected response:
250
+
91
251
  ```json
92
252
  {
93
253
  "success": true,
94
254
  "config": {
95
- "appId": "com.google.android.youtube",
255
+ "appId": "com.your.app.package",
96
256
  "platform": "android"
97
257
  }
98
258
  }
99
259
  ```
100
260
 
101
- ### 2. List Prompt Files
261
+ ### 3. List Prompt Files
102
262
 
103
263
  Ask the AI:
264
+
104
265
  ```
105
266
  List available prompt files
106
267
  ```
107
268
 
108
- ### 3. Run a Test
269
+ ### 4. Run a Test
109
270
 
110
271
  Ask the AI:
272
+
111
273
  ```
112
- Run a test: "Test that user can open YouTube and search for TechGuru"
274
+ Run a test: Open the app and verify the login screen is visible
113
275
  ```
114
276
 
115
277
  ---
116
278
 
117
279
  ## Environment Variables
118
280
 
119
- You can pass environment variables in the MCP config:
281
+ | Variable | Required | Description |
282
+ | ---------------------- | ----------- | ----------------------------------------- |
283
+ | `APP_ID` | Yes | Target app package ID |
284
+ | `ANDROID_HOME` | Recommended | Android SDK path |
285
+ | `DEFAULT_WAIT_TIMEOUT` | No | Element wait timeout (ms, default: 10000) |
286
+ | `DEFAULT_RETRIES` | No | Auto-retry count (default: 1) |
287
+ | `MAX_RESULTS` | No | Max result files to keep (default: 50) |
288
+
289
+ Example config with all options:
120
290
 
121
291
  ```json
122
292
  {
123
293
  "mcpServers": {
124
294
  "maestro": {
125
- "command": "node",
126
- "args": ["path/to/src/mcp-server/index.js"],
295
+ "command": "maestro-mcp",
127
296
  "env": {
128
297
  "APP_ID": "com.mycompany.myapp",
129
298
  "ANDROID_HOME": "C:/Users/Me/Android/Sdk",
130
- "LOG_LEVEL": "debug"
299
+ "DEFAULT_WAIT_TIMEOUT": "15000",
300
+ "DEFAULT_RETRIES": "2",
301
+ "MAX_RESULTS": "100"
131
302
  }
132
303
  }
133
304
  }
134
305
  }
135
306
  ```
136
307
 
137
- Or create a `.env` file in the project root.
138
-
139
308
  ---
140
309
 
141
310
  ## Troubleshooting
142
311
 
312
+ ### Prerequisites Check Failed
313
+
314
+ Run the manual check to see what's missing:
315
+
316
+ ```bash
317
+ npm run check
318
+ ```
319
+
143
320
  ### Server doesn't start
144
321
 
145
- 1. Check Node.js version: `node --version` (must be 18+)
146
- 2. Check dependencies: `npm install`
147
- 3. Check for errors in `output/logs/mcp-server.log`
322
+ 1. Check prerequisites: `npm run check`
323
+ 2. Check Node.js version: `node --version` (must be 18+)
324
+ 3. Check Java version: `java --version` (must be 17+)
325
+ 4. Check Maestro: `maestro --version`
148
326
 
149
- ### Tools not appearing
327
+ ### Tools not appearing in AI chat
150
328
 
151
- 1. Restart your editor
329
+ 1. Restart your editor completely
152
330
  2. Check the MCP config path is correct
153
- 3. Verify the server path in the config
331
+ 3. Verify the command/args in the config
332
+ 4. Check for JSON syntax errors in config
154
333
 
155
334
  ### Tests fail with "No emulator"
156
335
 
157
336
  1. Start an Android emulator
158
337
  2. Verify with `adb devices`
338
+ 3. Make sure device shows as "device" not "offline"
159
339
 
160
340
  ### "APP_ID not configured"
161
341
 
162
342
  Set the `APP_ID` in either:
343
+
163
344
  - The MCP config's `env` section
164
345
  - A `.env` file in the project root
165
346
 
347
+ ### "ADB not found"
348
+
349
+ Set `ANDROID_HOME` in your MCP config:
350
+
351
+ ```json
352
+ "env": {
353
+ "ANDROID_HOME": "C:/Users/YourName/AppData/Local/Android/Sdk"
354
+ }
355
+ ```
356
+
166
357
  ---
167
358
 
168
359
  ## Available Tools
169
360
 
170
- | Tool | Description |
171
- |------|-------------|
172
- | `read_prompt_file` | Read test prompts from a file |
173
- | `list_prompt_files` | List available prompt files |
174
- | `get_app_config` | Get app configuration |
175
- | `validate_maestro_yaml` | Validate Maestro YAML |
176
- | `run_test` | Run a single test |
177
- | `run_test_suite` | Run multiple tests |
178
- | `get_test_results` | Get test results |
179
- | `take_screenshot` | Take a screenshot |
180
-
361
+ | Tool | Description |
362
+ | ----------------------- | ---------------------------------- |
363
+ | `read_prompt_file` | Read test prompts from a file |
364
+ | `list_prompt_files` | List available prompt files |
365
+ | `list_devices` | List connected Android devices |
366
+ | `select_device` | Select a specific device |
367
+ | `clear_device` | Clear device selection |
368
+ | `check_device` | Check device connection |
369
+ | `check_app` | Check if app is installed |
370
+ | `get_app_config` | Get app configuration |
371
+ | `validate_maestro_yaml` | Validate Maestro YAML |
372
+ | `run_test` | Run a single test |
373
+ | `run_test_suite` | Run multiple tests |
374
+ | `get_test_results` | Get test results |
375
+ | `take_screenshot` | Take a screenshot |
376
+ | `cleanup_results` | Clean up old results |
377
+ | `register_elements` | Register UI elements for better AI |
378
+ | `register_screen` | Register screen structure |
379
+ | `save_successful_flow` | Save working flow pattern |
380
+ | `get_saved_flows` | Get saved patterns |
381
+ | `delete_flow` | Delete a saved pattern |
382
+ | `get_ai_context` | Get context for AI generation |
383
+ | `get_full_context` | Get complete context data |
384
+ | `clear_app_context` | Clear app context |
385
+ | `list_app_contexts` | List apps with context |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-maestro-mobile-ai",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "private": false,
5
5
  "description": "MCP Server for AI-Assisted Mobile Automation using Maestro - Run mobile tests with natural language prompts",
6
6
  "main": "src/mcp-server/index.js",
@@ -10,6 +10,7 @@
10
10
  },
11
11
  "files": [
12
12
  "src/",
13
+ "scripts/",
13
14
  "prompts/",
14
15
  "templates/",
15
16
  "docs/",
@@ -32,6 +33,8 @@
32
33
  "dev": "node --watch src/mcp-server/index.js",
33
34
  "test:flow": "maestro test",
34
35
  "lint": "eslint src/",
36
+ "postinstall": "node scripts/check-prerequisites.js",
37
+ "check": "node scripts/check-prerequisites.js",
35
38
  "prepublishOnly": "npm run lint"
36
39
  },
37
40
  "keywords": [
@@ -0,0 +1,277 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Postinstall Prerequisites Check
5
+ *
6
+ * This script runs after npm install to warn users about missing prerequisites.
7
+ * It shows warnings but does NOT block installation.
8
+ */
9
+
10
+ import { execSync } from "child_process";
11
+
12
+ // ANSI color codes for terminal output
13
+ const colors = {
14
+ reset: "\x1b[0m",
15
+ bright: "\x1b[1m",
16
+ red: "\x1b[31m",
17
+ green: "\x1b[32m",
18
+ yellow: "\x1b[33m",
19
+ blue: "\x1b[34m",
20
+ cyan: "\x1b[36m",
21
+ };
22
+
23
+ const icons = {
24
+ check: "✅",
25
+ cross: "❌",
26
+ warning: "⚠️",
27
+ info: "ℹ️",
28
+ };
29
+
30
+ /**
31
+ * Execute a command and return the output
32
+ */
33
+ function execCommand(command) {
34
+ try {
35
+ return execSync(command, {
36
+ encoding: "utf8",
37
+ timeout: 10000,
38
+ stdio: ["pipe", "pipe", "pipe"]
39
+ }).trim();
40
+ } catch (error) {
41
+ return null;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Parse version string to extract major version number
47
+ */
48
+ function parseVersion(versionString) {
49
+ if (!versionString) return null;
50
+ const match = versionString.match(/(\d+)\.(\d+)/);
51
+ if (match) {
52
+ return {
53
+ major: parseInt(match[1], 10),
54
+ minor: parseInt(match[2], 10),
55
+ full: versionString,
56
+ };
57
+ }
58
+ return null;
59
+ }
60
+
61
+ /**
62
+ * Check Node.js version
63
+ */
64
+ function checkNodeJs() {
65
+ const version = process.version;
66
+ const parsed = parseVersion(version);
67
+
68
+ if (!parsed) {
69
+ return {
70
+ installed: false,
71
+ message: "Could not determine Node.js version"
72
+ };
73
+ }
74
+
75
+ if (parsed.major >= 18) {
76
+ return {
77
+ installed: true,
78
+ version: version,
79
+ message: `Node.js ${version}`
80
+ };
81
+ }
82
+
83
+ return {
84
+ installed: true,
85
+ version: version,
86
+ outdated: true,
87
+ message: `Node.js ${version} (requires 18+)`
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Check Java version
93
+ */
94
+ function checkJava() {
95
+ // Try java --version first (Java 9+)
96
+ let output = execCommand("java --version");
97
+
98
+ // Fall back to java -version (older format)
99
+ if (!output) {
100
+ output = execCommand("java -version 2>&1");
101
+ }
102
+
103
+ if (!output) {
104
+ return {
105
+ installed: false,
106
+ message: "Java not found",
107
+ hint: "Install Java 17+ from https://adoptium.net/"
108
+ };
109
+ }
110
+
111
+ // Parse Java version
112
+ const versionMatch = output.match(/(?:java|openjdk)\s+(?:version\s+)?["']?(\d+)(?:\.(\d+))?/i);
113
+ if (versionMatch) {
114
+ const major = parseInt(versionMatch[1], 10);
115
+
116
+ if (major >= 17) {
117
+ return {
118
+ installed: true,
119
+ version: `${major}`,
120
+ message: `Java ${major}`
121
+ };
122
+ }
123
+
124
+ return {
125
+ installed: true,
126
+ version: `${major}`,
127
+ outdated: true,
128
+ message: `Java ${major} (requires 17+)`,
129
+ hint: "Upgrade to Java 17+ from https://adoptium.net/"
130
+ };
131
+ }
132
+
133
+ return {
134
+ installed: true,
135
+ message: "Java (version unknown)"
136
+ };
137
+ }
138
+
139
+ /**
140
+ * Check Maestro CLI
141
+ */
142
+ function checkMaestro() {
143
+ const output = execCommand("maestro --version");
144
+
145
+ if (!output) {
146
+ return {
147
+ installed: false,
148
+ message: "Maestro CLI not found",
149
+ hint: "Install: curl -Ls https://get.maestro.mobile.dev | bash"
150
+ };
151
+ }
152
+
153
+ return {
154
+ installed: true,
155
+ version: output,
156
+ message: `Maestro ${output}`
157
+ };
158
+ }
159
+
160
+ /**
161
+ * Check Android SDK / ADB
162
+ */
163
+ function checkAndroidSdk() {
164
+ const androidHome = process.env.ANDROID_HOME;
165
+
166
+ if (!androidHome) {
167
+ return {
168
+ installed: false,
169
+ message: "ANDROID_HOME not set",
170
+ hint: "Set ANDROID_HOME environment variable to your Android SDK path"
171
+ };
172
+ }
173
+
174
+ // Check if ADB exists
175
+ const adbOutput = execCommand("adb --version");
176
+
177
+ if (!adbOutput) {
178
+ return {
179
+ installed: true,
180
+ message: "ANDROID_HOME set but ADB not in PATH",
181
+ hint: "Add Android SDK platform-tools to your PATH"
182
+ };
183
+ }
184
+
185
+ return {
186
+ installed: true,
187
+ message: `Android SDK (${androidHome.length > 40 ? "..." + androidHome.slice(-37) : androidHome})`
188
+ };
189
+ }
190
+
191
+ /**
192
+ * Main check function
193
+ */
194
+ function runChecks() {
195
+ console.log("");
196
+ console.log(`${colors.cyan}${colors.bright}╔════════════════════════════════════════════════════════╗${colors.reset}`);
197
+ console.log(`${colors.cyan}${colors.bright}║ MCP Maestro Mobile AI - Prerequisites ║${colors.reset}`);
198
+ console.log(`${colors.cyan}${colors.bright}╚════════════════════════════════════════════════════════╝${colors.reset}`);
199
+ console.log("");
200
+
201
+ const checks = [
202
+ { name: "Node.js 18+", check: checkNodeJs, required: true },
203
+ { name: "Java 17+", check: checkJava, required: true },
204
+ { name: "Maestro CLI", check: checkMaestro, required: true },
205
+ { name: "Android SDK", check: checkAndroidSdk, required: false },
206
+ ];
207
+
208
+ const results = [];
209
+ let hasErrors = false;
210
+ let hasWarnings = false;
211
+
212
+ for (const item of checks) {
213
+ const result = item.check();
214
+ results.push({ ...item, result });
215
+
216
+ let icon, color;
217
+
218
+ if (result.installed && !result.outdated) {
219
+ icon = icons.check;
220
+ color = colors.green;
221
+ } else if (result.outdated) {
222
+ icon = icons.warning;
223
+ color = colors.yellow;
224
+ hasWarnings = true;
225
+ if (item.required) hasErrors = true;
226
+ } else {
227
+ icon = item.required ? icons.cross : icons.warning;
228
+ color = item.required ? colors.red : colors.yellow;
229
+ if (item.required) {
230
+ hasErrors = true;
231
+ } else {
232
+ hasWarnings = true;
233
+ }
234
+ }
235
+
236
+ console.log(` ${icon} ${color}${result.message}${colors.reset}`);
237
+ }
238
+
239
+ console.log("");
240
+
241
+ // Show hints for missing/outdated items
242
+ const hints = results
243
+ .filter(r => r.result.hint)
244
+ .map(r => r.result.hint);
245
+
246
+ if (hints.length > 0) {
247
+ console.log(`${colors.yellow}${icons.info} Installation hints:${colors.reset}`);
248
+ hints.forEach(hint => {
249
+ console.log(` ${colors.yellow}→ ${hint}${colors.reset}`);
250
+ });
251
+ console.log("");
252
+ }
253
+
254
+ // Summary
255
+ if (hasErrors) {
256
+ console.log(`${colors.red}${colors.bright}⚠️ Some required prerequisites are missing.${colors.reset}`);
257
+ console.log(`${colors.red} The server will not start without them.${colors.reset}`);
258
+ console.log("");
259
+ } else if (hasWarnings) {
260
+ console.log(`${colors.yellow}${colors.bright}${icons.warning} Some optional prerequisites are missing.${colors.reset}`);
261
+ console.log(`${colors.yellow} The server may have limited functionality.${colors.reset}`);
262
+ console.log("");
263
+ } else {
264
+ console.log(`${colors.green}${colors.bright}${icons.check} All prerequisites satisfied!${colors.reset}`);
265
+ console.log("");
266
+ }
267
+
268
+ console.log(`${colors.cyan}Documentation: https://github.com/krunal-mahera/mcp-maestro-mobile-ai${colors.reset}`);
269
+ console.log("");
270
+
271
+ // Note: We do NOT exit with error code - this is just a warning
272
+ // The actual validation happens at runtime
273
+ }
274
+
275
+ // Run checks
276
+ runChecks();
277
+
@@ -47,6 +47,7 @@ import {
47
47
  listContexts,
48
48
  } from "./tools/contextTools.js";
49
49
  import { logger } from "./utils/logger.js";
50
+ import { validatePrerequisites } from "./utils/prerequisites.js";
50
51
 
51
52
  // Load environment variables
52
53
  const __filename = fileURLToPath(import.meta.url);
@@ -612,6 +613,17 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
612
613
 
613
614
  async function main() {
614
615
  logger.info("Starting MCP Maestro Mobile AI v1.1.0...");
616
+ logger.info("");
617
+
618
+ // Validate prerequisites before starting
619
+ // This will exit with code 2 if critical prerequisites are missing
620
+ await validatePrerequisites({
621
+ exitOnError: true,
622
+ checkDevice: false, // Don't require device at startup
623
+ });
624
+
625
+ logger.info("");
626
+ logger.info("Prerequisites validated. Starting server...");
615
627
 
616
628
  const transport = new StdioServerTransport();
617
629
  await server.connect(transport);
@@ -0,0 +1,390 @@
1
+ /**
2
+ * Runtime Prerequisites Validation
3
+ *
4
+ * This module validates prerequisites when the MCP server starts.
5
+ * Critical missing dependencies will prevent server startup.
6
+ */
7
+
8
+ import { execSync } from "child_process";
9
+ import path from "path";
10
+ import { logger } from "./logger.js";
11
+
12
+ /**
13
+ * Execute a command safely and return result
14
+ */
15
+ function execCommand(command, options = {}) {
16
+ try {
17
+ return {
18
+ success: true,
19
+ output: execSync(command, {
20
+ encoding: "utf8",
21
+ timeout: options.timeout || 10000,
22
+ stdio: ["pipe", "pipe", "pipe"],
23
+ ...options,
24
+ }).trim(),
25
+ };
26
+ } catch (error) {
27
+ return {
28
+ success: false,
29
+ error: error.message,
30
+ };
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Parse version string to extract major version number
36
+ */
37
+ function parseVersion(versionString) {
38
+ if (!versionString) return null;
39
+ const match = versionString.match(/(\d+)(?:\.(\d+))?/);
40
+ if (match) {
41
+ return {
42
+ major: parseInt(match[1], 10),
43
+ minor: match[2] ? parseInt(match[2], 10) : 0,
44
+ };
45
+ }
46
+ return null;
47
+ }
48
+
49
+ /**
50
+ * Check Node.js version (should always pass since we're running in Node)
51
+ */
52
+ function checkNodeJs() {
53
+ const version = process.version;
54
+ const parsed = parseVersion(version);
55
+
56
+ if (!parsed || parsed.major < 18) {
57
+ return {
58
+ name: "Node.js",
59
+ status: "error",
60
+ installed: true,
61
+ version: version,
62
+ required: "18+",
63
+ message: `Node.js ${version} is outdated. Requires 18+.`,
64
+ hint: "Upgrade Node.js from https://nodejs.org/",
65
+ };
66
+ }
67
+
68
+ return {
69
+ name: "Node.js",
70
+ status: "ok",
71
+ installed: true,
72
+ version: version,
73
+ message: `Node.js ${version}`,
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Check Java version
79
+ */
80
+ function checkJava() {
81
+ // Try java --version first (Java 9+)
82
+ let result = execCommand("java --version");
83
+
84
+ // Fall back to java -version
85
+ if (!result.success) {
86
+ result = execCommand("java -version 2>&1");
87
+ }
88
+
89
+ if (!result.success) {
90
+ return {
91
+ name: "Java",
92
+ status: "error",
93
+ installed: false,
94
+ required: "17+",
95
+ message: "Java is not installed or not in PATH.",
96
+ hint: "Install Java 17+ from https://adoptium.net/",
97
+ };
98
+ }
99
+
100
+ // Parse version
101
+ const versionMatch = result.output.match(
102
+ /(?:java|openjdk)\s+(?:version\s+)?["']?(\d+)/i
103
+ );
104
+
105
+ if (versionMatch) {
106
+ const major = parseInt(versionMatch[1], 10);
107
+
108
+ if (major < 17) {
109
+ return {
110
+ name: "Java",
111
+ status: "error",
112
+ installed: true,
113
+ version: `${major}`,
114
+ required: "17+",
115
+ message: `Java ${major} is outdated. Requires 17+.`,
116
+ hint: "Upgrade to Java 17+ from https://adoptium.net/",
117
+ };
118
+ }
119
+
120
+ return {
121
+ name: "Java",
122
+ status: "ok",
123
+ installed: true,
124
+ version: `${major}`,
125
+ message: `Java ${major}`,
126
+ };
127
+ }
128
+
129
+ // Can't determine version but Java is installed
130
+ return {
131
+ name: "Java",
132
+ status: "warning",
133
+ installed: true,
134
+ version: "unknown",
135
+ message: "Java installed (version unknown)",
136
+ };
137
+ }
138
+
139
+ /**
140
+ * Check Maestro CLI
141
+ */
142
+ function checkMaestro() {
143
+ const result = execCommand("maestro --version");
144
+
145
+ if (!result.success) {
146
+ return {
147
+ name: "Maestro CLI",
148
+ status: "error",
149
+ installed: false,
150
+ message: "Maestro CLI is not installed or not in PATH.",
151
+ hint: "Install Maestro: curl -Ls https://get.maestro.mobile.dev | bash",
152
+ hintWindows: "Install Maestro: iwr https://get.maestro.mobile.dev | iex",
153
+ };
154
+ }
155
+
156
+ return {
157
+ name: "Maestro CLI",
158
+ status: "ok",
159
+ installed: true,
160
+ version: result.output,
161
+ message: `Maestro ${result.output}`,
162
+ };
163
+ }
164
+
165
+ /**
166
+ * Check Android SDK / ADB
167
+ */
168
+ function checkAndroidSdk() {
169
+ const androidHome = process.env.ANDROID_HOME;
170
+
171
+ if (!androidHome) {
172
+ return {
173
+ name: "Android SDK",
174
+ status: "warning",
175
+ installed: false,
176
+ message: "ANDROID_HOME environment variable is not set.",
177
+ hint: "Set ANDROID_HOME to your Android SDK path for reliable ADB access.",
178
+ };
179
+ }
180
+
181
+ // Check if ADB is accessible
182
+ const adbPath = path.join(androidHome, "platform-tools", "adb");
183
+ const result = execCommand(`"${adbPath}" --version`);
184
+
185
+ if (!result.success) {
186
+ // Try system PATH
187
+ const pathResult = execCommand("adb --version");
188
+ if (pathResult.success) {
189
+ return {
190
+ name: "Android SDK",
191
+ status: "ok",
192
+ installed: true,
193
+ message: `Android SDK (ADB available via PATH)`,
194
+ };
195
+ }
196
+
197
+ return {
198
+ name: "Android SDK",
199
+ status: "warning",
200
+ installed: true,
201
+ message: "ANDROID_HOME set but ADB not accessible.",
202
+ hint: "Ensure platform-tools is installed in Android SDK.",
203
+ };
204
+ }
205
+
206
+ return {
207
+ name: "Android SDK",
208
+ status: "ok",
209
+ installed: true,
210
+ path: androidHome,
211
+ message: `Android SDK configured`,
212
+ };
213
+ }
214
+
215
+ /**
216
+ * Check for connected device/emulator
217
+ */
218
+ function checkDevice() {
219
+ const androidHome = process.env.ANDROID_HOME;
220
+ let adbCmd = "adb";
221
+
222
+ if (androidHome) {
223
+ adbCmd = `"${path.join(androidHome, "platform-tools", "adb")}"`;
224
+ }
225
+
226
+ const result = execCommand(`${adbCmd} devices`);
227
+
228
+ if (!result.success) {
229
+ return {
230
+ name: "Device/Emulator",
231
+ status: "warning",
232
+ connected: false,
233
+ message: "Could not check for connected devices.",
234
+ hint: "Ensure ADB is properly configured.",
235
+ };
236
+ }
237
+
238
+ // Parse device list
239
+ const lines = result.output.split("\n").filter((l) => l.trim());
240
+ const devices = lines
241
+ .slice(1)
242
+ .filter((l) => l.includes("device") && !l.includes("offline"))
243
+ .map((l) => l.split("\t")[0]);
244
+
245
+ if (devices.length === 0) {
246
+ return {
247
+ name: "Device/Emulator",
248
+ status: "warning",
249
+ connected: false,
250
+ message: "No Android device or emulator connected.",
251
+ hint: "Start an emulator or connect a device via USB.",
252
+ };
253
+ }
254
+
255
+ return {
256
+ name: "Device/Emulator",
257
+ status: "ok",
258
+ connected: true,
259
+ devices: devices,
260
+ message: `${devices.length} device(s) connected`,
261
+ };
262
+ }
263
+
264
+ /**
265
+ * Run all prerequisite checks
266
+ * @param {Object} options - Options
267
+ * @param {boolean} options.exitOnError - Exit process if critical error found
268
+ * @param {boolean} options.checkDevice - Also check for connected device
269
+ * @returns {Object} Results object
270
+ */
271
+ export async function validatePrerequisites(options = {}) {
272
+ const { exitOnError = true, checkDevice: shouldCheckDevice = false } = options;
273
+
274
+ logger.info("Validating prerequisites...");
275
+
276
+ const checks = [
277
+ { fn: checkNodeJs, critical: true },
278
+ { fn: checkJava, critical: true },
279
+ { fn: checkMaestro, critical: true },
280
+ { fn: checkAndroidSdk, critical: false },
281
+ ];
282
+
283
+ if (shouldCheckDevice) {
284
+ checks.push({ fn: checkDevice, critical: false });
285
+ }
286
+
287
+ const results = [];
288
+ let hasErrors = false;
289
+ let hasWarnings = false;
290
+
291
+ for (const { fn, critical } of checks) {
292
+ const result = fn();
293
+ results.push({ ...result, critical });
294
+
295
+ if (result.status === "error") {
296
+ if (critical) {
297
+ hasErrors = true;
298
+ logger.error(`❌ ${result.name}: ${result.message}`);
299
+ } else {
300
+ hasWarnings = true;
301
+ logger.warn(`⚠️ ${result.name}: ${result.message}`);
302
+ }
303
+ } else if (result.status === "warning") {
304
+ hasWarnings = true;
305
+ logger.warn(`⚠️ ${result.name}: ${result.message}`);
306
+ } else {
307
+ logger.info(`✅ ${result.name}: ${result.message}`);
308
+ }
309
+ }
310
+
311
+ // Collect hints for failed checks
312
+ const hints = results
313
+ .filter((r) => r.hint && (r.status === "error" || r.status === "warning"))
314
+ .map((r) => {
315
+ // Use Windows hint if available and on Windows
316
+ if (process.platform === "win32" && r.hintWindows) {
317
+ return r.hintWindows;
318
+ }
319
+ return r.hint;
320
+ });
321
+
322
+ if (hints.length > 0) {
323
+ logger.info("");
324
+ logger.info("💡 To fix issues:");
325
+ hints.forEach((hint) => {
326
+ logger.info(` → ${hint}`);
327
+ });
328
+ }
329
+
330
+ // Handle critical errors
331
+ if (hasErrors && exitOnError) {
332
+ logger.error("");
333
+ logger.error("═══════════════════════════════════════════════════════");
334
+ logger.error(" CRITICAL: Required prerequisites are missing.");
335
+ logger.error(" The MCP server cannot start without them.");
336
+ logger.error("═══════════════════════════════════════════════════════");
337
+ logger.error("");
338
+ process.exit(2); // Exit code 2 for infrastructure errors
339
+ }
340
+
341
+ return {
342
+ success: !hasErrors,
343
+ hasWarnings,
344
+ results,
345
+ hints,
346
+ };
347
+ }
348
+
349
+ /**
350
+ * Quick check for critical prerequisites only
351
+ * Used for fast startup validation
352
+ */
353
+ export function quickCheck() {
354
+ const java = checkJava();
355
+ const maestro = checkMaestro();
356
+
357
+ const errors = [];
358
+
359
+ if (java.status === "error") {
360
+ errors.push({
361
+ name: "Java",
362
+ message: java.message,
363
+ hint: java.hint,
364
+ });
365
+ }
366
+
367
+ if (maestro.status === "error") {
368
+ errors.push({
369
+ name: "Maestro CLI",
370
+ message: maestro.message,
371
+ hint: process.platform === "win32" ? maestro.hintWindows : maestro.hint,
372
+ });
373
+ }
374
+
375
+ return {
376
+ success: errors.length === 0,
377
+ errors,
378
+ };
379
+ }
380
+
381
+ export default {
382
+ validatePrerequisites,
383
+ quickCheck,
384
+ checkNodeJs,
385
+ checkJava,
386
+ checkMaestro,
387
+ checkAndroidSdk,
388
+ checkDevice,
389
+ };
390
+