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 +6 -0
- package/docs/MCP_SETUP.md +246 -41
- package/package.json +4 -1
- package/scripts/check-prerequisites.js +277 -0
- package/src/mcp-server/index.js +12 -0
- package/src/mcp-server/utils/prerequisites.js +390 -0
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
|
|
3
|
+
## Setting up MCP Maestro Mobile AI with AI Clients
|
|
4
4
|
|
|
5
|
-
This guide explains how to configure the Maestro
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
126
|
+
**If installed via NPM (global):**
|
|
31
127
|
|
|
32
128
|
```json
|
|
33
129
|
{
|
|
34
130
|
"mcpServers": {
|
|
35
131
|
"maestro": {
|
|
36
|
-
"command": "
|
|
37
|
-
"
|
|
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.
|
|
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
|
-
**
|
|
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
|
|
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
|
|
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.
|
|
255
|
+
"appId": "com.your.app.package",
|
|
96
256
|
"platform": "android"
|
|
97
257
|
}
|
|
98
258
|
}
|
|
99
259
|
```
|
|
100
260
|
|
|
101
|
-
###
|
|
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
|
-
###
|
|
269
|
+
### 4. Run a Test
|
|
109
270
|
|
|
110
271
|
Ask the AI:
|
|
272
|
+
|
|
111
273
|
```
|
|
112
|
-
Run a test:
|
|
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
|
-
|
|
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": "
|
|
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
|
-
"
|
|
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
|
|
146
|
-
2. Check
|
|
147
|
-
3. Check
|
|
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
|
|
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
|
|
171
|
-
|
|
172
|
-
| `read_prompt_file`
|
|
173
|
-
| `list_prompt_files`
|
|
174
|
-
| `
|
|
175
|
-
| `
|
|
176
|
-
| `
|
|
177
|
-
| `
|
|
178
|
-
| `
|
|
179
|
-
| `
|
|
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.
|
|
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
|
+
|
package/src/mcp-server/index.js
CHANGED
|
@@ -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
|
+
|