mcp-maestro-mobile-ai 1.1.0 → 1.3.0

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.
@@ -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
+
@@ -23,7 +23,7 @@ import { dirname, join } from "path";
23
23
  // Import tools
24
24
  import { readPromptFile, listPromptFiles } from "./tools/promptTools.js";
25
25
  import { validateMaestroYaml } from "./tools/validateTools.js";
26
- import { runTest, runTestSuite } from "./tools/runTools.js";
26
+ import { runTest, runTestSuite, generateTestReport, listTestReports, runTestSuiteWithReport } from "./tools/runTools.js";
27
27
  import {
28
28
  getAppConfig,
29
29
  getTestResults,
@@ -45,8 +45,13 @@ import {
45
45
  getAppContext,
46
46
  clearContext,
47
47
  listContexts,
48
+ getYamlInstructions,
49
+ validateYamlBeforeRun,
50
+ getTestPattern,
51
+ getScreenAnalysis,
48
52
  } from "./tools/contextTools.js";
49
53
  import { logger } from "./utils/logger.js";
54
+ import { validatePrerequisites } from "./utils/prerequisites.js";
50
55
 
51
56
  // Load environment variables
52
57
  const __filename = fileURLToPath(import.meta.url);
@@ -57,7 +62,7 @@ config({ path: join(__dirname, "../../.env") });
57
62
  const server = new Server(
58
63
  {
59
64
  name: "mcp-maestro-mobile-ai",
60
- version: "1.1.0",
65
+ version: "1.2.0",
61
66
  },
62
67
  {
63
68
  capabilities: {
@@ -179,7 +184,7 @@ const TOOLS = [
179
184
  {
180
185
  name: "validate_maestro_yaml",
181
186
  description:
182
- "Validate a Maestro YAML flow for syntax errors before running. Returns validation result with any errors found.",
187
+ "Validate Maestro YAML for syntax AND structure errors. Checks for: missing appId, missing clearState/launchApp, inputText without tapOn (which causes text to go to wrong fields). Always validate before running!",
183
188
  inputSchema: {
184
189
  type: "object",
185
190
  properties: {
@@ -195,14 +200,31 @@ const TOOLS = [
195
200
  // === Test Execution Tools ===
196
201
  {
197
202
  name: "run_test",
198
- description:
199
- "Run a single Maestro test. Provide the Maestro YAML content directly. The test will be executed on the selected device (or first available if none selected). Includes pre-flight checks and auto-retry.",
203
+ description: `Run a single Maestro test. IMPORTANT: The YAML MUST follow these rules or it will be REJECTED:
204
+
205
+ 1. STRUCTURE: Must start with appId, then clearState, then launchApp
206
+ 2. TEXT INPUT: ALWAYS use tapOn BEFORE inputText (or text goes to wrong field!)
207
+ CORRECT: - tapOn: "Username" then - inputText: "value"
208
+ WRONG: - inputText: "value" (missing tapOn!)
209
+ 3. Use visible text labels for elements when testIDs are unknown
210
+
211
+ Example valid YAML:
212
+ appId: com.example.app
213
+ ---
214
+ - clearState
215
+ - launchApp
216
+ - tapOn: "Username"
217
+ - inputText: "user@example.com"
218
+ - tapOn: "Password"
219
+ - inputText: "password123"
220
+ - tapOn: "Sign In"
221
+ - assertVisible: "Welcome"`,
200
222
  inputSchema: {
201
223
  type: "object",
202
224
  properties: {
203
225
  yaml: {
204
226
  type: "string",
205
- description: "The Maestro YAML flow content to run",
227
+ description: "The Maestro YAML flow content. MUST use tapOn before inputText for each field!",
206
228
  },
207
229
  name: {
208
230
  type: "string",
@@ -220,7 +242,7 @@ const TOOLS = [
220
242
  {
221
243
  name: "run_test_suite",
222
244
  description:
223
- "Run multiple Maestro tests in sequence. Provide an array of test objects with yaml and name properties. Tests run on selected device.",
245
+ "Run multiple Maestro tests in sequence. Each YAML must follow the rules: appId at top, clearState, launchApp, and ALWAYS tapOn before inputText!",
224
246
  inputSchema: {
225
247
  type: "object",
226
248
  properties: {
@@ -231,7 +253,7 @@ const TOOLS = [
231
253
  properties: {
232
254
  yaml: {
233
255
  type: "string",
234
- description: "The Maestro YAML flow content",
256
+ description: "The Maestro YAML. MUST use tapOn before inputText!",
235
257
  },
236
258
  name: {
237
259
  type: "string",
@@ -252,7 +274,80 @@ const TOOLS = [
252
274
  },
253
275
  },
254
276
 
255
- // === Results & Utility Tools ===
277
+ // === Results & Reporting Tools ===
278
+ {
279
+ name: "run_tests_with_report",
280
+ description:
281
+ "Run multiple tests and automatically generate HTML + JSON report. Use this when running tests from a prompt file. Returns report path that can be opened in browser.",
282
+ inputSchema: {
283
+ type: "object",
284
+ properties: {
285
+ tests: {
286
+ type: "array",
287
+ items: {
288
+ type: "object",
289
+ properties: {
290
+ yaml: {
291
+ type: "string",
292
+ description: "The Maestro YAML. MUST use tapOn before inputText!",
293
+ },
294
+ name: {
295
+ type: "string",
296
+ description: "Name for this test",
297
+ },
298
+ },
299
+ required: ["yaml", "name"],
300
+ },
301
+ description: "Array of tests to run",
302
+ },
303
+ promptFile: {
304
+ type: "string",
305
+ description: "Name of the prompt file (for report metadata)",
306
+ },
307
+ appId: {
308
+ type: "string",
309
+ description: "App ID (for report metadata)",
310
+ },
311
+ retries: {
312
+ type: "number",
313
+ description: "Number of retries for failed tests",
314
+ },
315
+ },
316
+ required: ["tests"],
317
+ },
318
+ },
319
+ {
320
+ name: "generate_report",
321
+ description:
322
+ "Generate HTML and JSON report from test results. Call this after running tests to create a visual summary report.",
323
+ inputSchema: {
324
+ type: "object",
325
+ properties: {
326
+ results: {
327
+ type: "array",
328
+ description: "Array of test results with name, success, duration, error fields",
329
+ },
330
+ promptFile: {
331
+ type: "string",
332
+ description: "Name of the prompt file",
333
+ },
334
+ appId: {
335
+ type: "string",
336
+ description: "App ID",
337
+ },
338
+ },
339
+ required: ["results"],
340
+ },
341
+ },
342
+ {
343
+ name: "list_reports",
344
+ description:
345
+ "List all generated test reports. Returns paths to HTML and JSON report files.",
346
+ inputSchema: {
347
+ type: "object",
348
+ properties: {},
349
+ },
350
+ },
256
351
  {
257
352
  name: "get_test_results",
258
353
  description: "Get the results from the last test run or a specific run by ID.",
@@ -457,6 +552,62 @@ const TOOLS = [
457
552
  properties: {},
458
553
  },
459
554
  },
555
+
556
+ // === YAML Generation Tools (CRITICAL) ===
557
+ {
558
+ name: "get_yaml_instructions",
559
+ description:
560
+ "CRITICAL: Call this BEFORE generating any Maestro YAML. Returns the exact rules and patterns for generating valid YAML that works consistently. Includes app-specific context if available.",
561
+ inputSchema: {
562
+ type: "object",
563
+ properties: {
564
+ appId: {
565
+ type: "string",
566
+ description: "App package ID to get app-specific context",
567
+ },
568
+ },
569
+ },
570
+ },
571
+ {
572
+ name: "validate_yaml_structure",
573
+ description:
574
+ "Validate YAML structure before running a test. Checks for common issues like missing 'tapOn' before 'inputText' which causes text to go to wrong fields.",
575
+ inputSchema: {
576
+ type: "object",
577
+ properties: {
578
+ yamlContent: {
579
+ type: "string",
580
+ description: "The Maestro YAML content to validate",
581
+ },
582
+ },
583
+ required: ["yamlContent"],
584
+ },
585
+ },
586
+ {
587
+ name: "get_test_pattern",
588
+ description:
589
+ "Get a standard test pattern template. Available: login, form, search, navigation, list, settings, logout. Use these as starting points.",
590
+ inputSchema: {
591
+ type: "object",
592
+ properties: {
593
+ patternName: {
594
+ type: "string",
595
+ description: "Pattern name: login, form, search, navigation, list, settings, or logout",
596
+ enum: ["login", "form", "search", "navigation", "list", "settings", "logout"],
597
+ },
598
+ },
599
+ required: ["patternName"],
600
+ },
601
+ },
602
+ {
603
+ name: "get_screen_analysis_help",
604
+ description:
605
+ "Get instructions on how to gather UI element information from the user. Call this when you don't know the exact element names/labels on a screen. Returns questions to ask the user.",
606
+ inputSchema: {
607
+ type: "object",
608
+ properties: {},
609
+ },
610
+ },
460
611
  ];
461
612
 
462
613
  // ============================================
@@ -514,7 +665,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
514
665
  case "run_test_suite":
515
666
  return await runTestSuite(args.tests, { retries: args.retries });
516
667
 
517
- // Results & utility tools
668
+ // Results & reporting tools
669
+ case "run_tests_with_report":
670
+ return await runTestSuiteWithReport(args.tests, {
671
+ promptFile: args.promptFile,
672
+ appId: args.appId,
673
+ retries: args.retries,
674
+ });
675
+
676
+ case "generate_report":
677
+ return await generateTestReport(args.results, {
678
+ promptFile: args.promptFile,
679
+ appId: args.appId,
680
+ });
681
+
682
+ case "list_reports":
683
+ return await listTestReports();
684
+
518
685
  case "get_test_results":
519
686
  return await getTestResults(args.runId);
520
687
 
@@ -555,6 +722,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
555
722
  case "list_app_contexts":
556
723
  return await listContexts();
557
724
 
725
+ // YAML generation tools
726
+ case "get_yaml_instructions":
727
+ return await getYamlInstructions(args.appId);
728
+
729
+ case "validate_yaml_structure":
730
+ return await validateYamlBeforeRun(args.yamlContent);
731
+
732
+ case "get_test_pattern":
733
+ return await getTestPattern(args.patternName);
734
+
735
+ case "get_screen_analysis_help":
736
+ return await getScreenAnalysis();
737
+
558
738
  default:
559
739
  throw new Error(`Unknown tool: ${name}`);
560
740
  }
@@ -611,7 +791,18 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
611
791
  // ============================================
612
792
 
613
793
  async function main() {
614
- logger.info("Starting MCP Maestro Mobile AI v1.1.0...");
794
+ logger.info("Starting MCP Maestro Mobile AI v1.2.0...");
795
+ logger.info("");
796
+
797
+ // Validate prerequisites before starting
798
+ // This will exit with code 2 if critical prerequisites are missing
799
+ await validatePrerequisites({
800
+ exitOnError: true,
801
+ checkDevice: false, // Don't require device at startup
802
+ });
803
+
804
+ logger.info("");
805
+ logger.info("Prerequisites validated. Starting server...");
615
806
 
616
807
  const transport = new StdioServerTransport();
617
808
  await server.connect(transport);