mcp-maestro-mobile-ai 1.3.0 → 1.4.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.
- package/CHANGELOG.md +244 -143
- package/ROADMAP.md +21 -8
- package/package.json +6 -3
- package/src/mcp-server/index.js +1059 -816
- package/src/mcp-server/schemas/toolSchemas.js +636 -0
- package/src/mcp-server/utils/maestro.js +265 -29
- package/src/mcp-server/utils/security.js +1200 -0
package/src/mcp-server/index.js
CHANGED
|
@@ -1,816 +1,1059 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Maestro MCP Server
|
|
5
|
-
* AI-Assisted Mobile Automation using Model Context Protocol
|
|
6
|
-
*
|
|
7
|
-
* This server exposes tools for running Maestro mobile tests
|
|
8
|
-
* that can be called by AI clients (Cursor, Claude Desktop, VS Code Copilot)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
import {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
},
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
},
|
|
442
|
-
},
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
},
|
|
601
|
-
},
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Maestro MCP Server
|
|
5
|
+
* AI-Assisted Mobile Automation using Model Context Protocol
|
|
6
|
+
*
|
|
7
|
+
* This server exposes tools for running Maestro mobile tests
|
|
8
|
+
* that can be called by AI clients (Cursor, Claude Desktop, VS Code Copilot)
|
|
9
|
+
*
|
|
10
|
+
* Security: All tool inputs are validated using Zod schemas before execution.
|
|
11
|
+
* See schemas/toolSchemas.js for validation rules.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
15
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
16
|
+
import {
|
|
17
|
+
CallToolRequestSchema,
|
|
18
|
+
ListToolsRequestSchema,
|
|
19
|
+
ListResourcesRequestSchema,
|
|
20
|
+
ReadResourceRequestSchema,
|
|
21
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
22
|
+
import { config } from "dotenv";
|
|
23
|
+
import { fileURLToPath } from "url";
|
|
24
|
+
import { dirname, join } from "path";
|
|
25
|
+
|
|
26
|
+
// Import tools
|
|
27
|
+
import { readPromptFile, listPromptFiles } from "./tools/promptTools.js";
|
|
28
|
+
import { validateMaestroYaml } from "./tools/validateTools.js";
|
|
29
|
+
import {
|
|
30
|
+
runTest,
|
|
31
|
+
runTestSuite,
|
|
32
|
+
generateTestReport,
|
|
33
|
+
listTestReports,
|
|
34
|
+
runTestSuiteWithReport,
|
|
35
|
+
} from "./tools/runTools.js";
|
|
36
|
+
import {
|
|
37
|
+
getAppConfig,
|
|
38
|
+
getTestResults,
|
|
39
|
+
takeScreenshot,
|
|
40
|
+
checkDevice,
|
|
41
|
+
checkApp,
|
|
42
|
+
cleanupResults,
|
|
43
|
+
listDevices,
|
|
44
|
+
selectDevice,
|
|
45
|
+
clearDevice,
|
|
46
|
+
} from "./tools/utilityTools.js";
|
|
47
|
+
import {
|
|
48
|
+
registerAppElements,
|
|
49
|
+
registerAppScreen,
|
|
50
|
+
saveFlow,
|
|
51
|
+
getFlows,
|
|
52
|
+
removeFlow,
|
|
53
|
+
getAIContext,
|
|
54
|
+
getAppContext,
|
|
55
|
+
clearContext,
|
|
56
|
+
listContexts,
|
|
57
|
+
getYamlInstructions,
|
|
58
|
+
validateYamlBeforeRun,
|
|
59
|
+
getTestPattern,
|
|
60
|
+
getScreenAnalysis,
|
|
61
|
+
} from "./tools/contextTools.js";
|
|
62
|
+
import { logger } from "./utils/logger.js";
|
|
63
|
+
import { validatePrerequisites } from "./utils/prerequisites.js";
|
|
64
|
+
|
|
65
|
+
// Import security and validation
|
|
66
|
+
import {
|
|
67
|
+
SecurityError,
|
|
68
|
+
isSafeModeEnabled,
|
|
69
|
+
getSecurityConfig,
|
|
70
|
+
logSecurityEvent,
|
|
71
|
+
} from "./utils/security.js";
|
|
72
|
+
import { validateToolInput } from "./schemas/toolSchemas.js";
|
|
73
|
+
|
|
74
|
+
// Load environment variables
|
|
75
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
76
|
+
const __dirname = dirname(__filename);
|
|
77
|
+
config({ path: join(__dirname, "../../.env") });
|
|
78
|
+
|
|
79
|
+
// Server version - updated for security features
|
|
80
|
+
const SERVER_VERSION = "1.4.0";
|
|
81
|
+
|
|
82
|
+
// Create MCP Server
|
|
83
|
+
const server = new Server(
|
|
84
|
+
{
|
|
85
|
+
name: "mcp-maestro-mobile-ai",
|
|
86
|
+
version: SERVER_VERSION,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
capabilities: {
|
|
90
|
+
tools: {},
|
|
91
|
+
resources: {},
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// ============================================
|
|
97
|
+
// TOOL DEFINITIONS
|
|
98
|
+
// ============================================
|
|
99
|
+
|
|
100
|
+
const TOOLS = [
|
|
101
|
+
// === Prompt File Tools ===
|
|
102
|
+
{
|
|
103
|
+
name: "read_prompt_file",
|
|
104
|
+
description:
|
|
105
|
+
"Read test prompts from a .txt or .md file. Each line in the file is treated as a separate test case prompt.",
|
|
106
|
+
inputSchema: {
|
|
107
|
+
type: "object",
|
|
108
|
+
properties: {
|
|
109
|
+
file: {
|
|
110
|
+
type: "string",
|
|
111
|
+
description:
|
|
112
|
+
'Path to the prompt file (e.g., "prompts/login-tests.txt")',
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
required: ["file"],
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "list_prompt_files",
|
|
120
|
+
description: "List all available prompt files in the prompts directory.",
|
|
121
|
+
inputSchema: {
|
|
122
|
+
type: "object",
|
|
123
|
+
properties: {
|
|
124
|
+
directory: {
|
|
125
|
+
type: "string",
|
|
126
|
+
description:
|
|
127
|
+
'Directory to search for prompt files (default: "prompts")',
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
// === Device Management Tools ===
|
|
134
|
+
{
|
|
135
|
+
name: "list_devices",
|
|
136
|
+
description:
|
|
137
|
+
"List all connected Android devices and emulators. Shows device ID, type (emulator/physical), and model name. Use this to see available devices before selecting one.",
|
|
138
|
+
inputSchema: {
|
|
139
|
+
type: "object",
|
|
140
|
+
properties: {},
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: "select_device",
|
|
145
|
+
description:
|
|
146
|
+
"Select a specific device to run tests on. All subsequent tests will run on this device until changed. Use list_devices first to see available device IDs.",
|
|
147
|
+
inputSchema: {
|
|
148
|
+
type: "object",
|
|
149
|
+
properties: {
|
|
150
|
+
deviceId: {
|
|
151
|
+
type: "string",
|
|
152
|
+
description:
|
|
153
|
+
'Device ID to select (e.g., "emulator-5554" or "RF8M12345XY" for physical device)',
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
required: ["deviceId"],
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: "clear_device",
|
|
161
|
+
description:
|
|
162
|
+
"Clear the device selection. Tests will run on the first available device (default behavior).",
|
|
163
|
+
inputSchema: {
|
|
164
|
+
type: "object",
|
|
165
|
+
properties: {},
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: "check_device",
|
|
170
|
+
description:
|
|
171
|
+
"Check if an Android emulator or device is connected. Shows connection status and which device is selected.",
|
|
172
|
+
inputSchema: {
|
|
173
|
+
type: "object",
|
|
174
|
+
properties: {},
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: "check_app",
|
|
179
|
+
description:
|
|
180
|
+
"Check if the target app is installed on the connected device. Verifies the app is ready for testing.",
|
|
181
|
+
inputSchema: {
|
|
182
|
+
type: "object",
|
|
183
|
+
properties: {
|
|
184
|
+
appId: {
|
|
185
|
+
type: "string",
|
|
186
|
+
description:
|
|
187
|
+
"Optional: App package ID to check. Uses configured APP_ID if not provided.",
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
// === Configuration Tools ===
|
|
194
|
+
{
|
|
195
|
+
name: "get_app_config",
|
|
196
|
+
description:
|
|
197
|
+
"Get the current app configuration including appId, platform, selected device, and other settings.",
|
|
198
|
+
inputSchema: {
|
|
199
|
+
type: "object",
|
|
200
|
+
properties: {},
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
// === Validation Tools ===
|
|
205
|
+
{
|
|
206
|
+
name: "validate_maestro_yaml",
|
|
207
|
+
description:
|
|
208
|
+
"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!",
|
|
209
|
+
inputSchema: {
|
|
210
|
+
type: "object",
|
|
211
|
+
properties: {
|
|
212
|
+
yaml: {
|
|
213
|
+
type: "string",
|
|
214
|
+
description: "The Maestro YAML content to validate",
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
required: ["yaml"],
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
// === Test Execution Tools ===
|
|
222
|
+
{
|
|
223
|
+
name: "run_test",
|
|
224
|
+
description: `Run a single Maestro test. IMPORTANT: The YAML MUST follow these rules or it will be REJECTED:
|
|
225
|
+
|
|
226
|
+
1. STRUCTURE: Must start with appId, then clearState, then launchApp
|
|
227
|
+
2. TEXT INPUT: ALWAYS use tapOn BEFORE inputText (or text goes to wrong field!)
|
|
228
|
+
CORRECT: - tapOn: "Username" then - inputText: "value"
|
|
229
|
+
WRONG: - inputText: "value" (missing tapOn!)
|
|
230
|
+
3. Use visible text labels for elements when testIDs are unknown
|
|
231
|
+
|
|
232
|
+
Example valid YAML:
|
|
233
|
+
appId: com.example.app
|
|
234
|
+
---
|
|
235
|
+
- clearState
|
|
236
|
+
- launchApp
|
|
237
|
+
- tapOn: "Username"
|
|
238
|
+
- inputText: "user@example.com"
|
|
239
|
+
- tapOn: "Password"
|
|
240
|
+
- inputText: "password123"
|
|
241
|
+
- tapOn: "Sign In"
|
|
242
|
+
- assertVisible: "Welcome"`,
|
|
243
|
+
inputSchema: {
|
|
244
|
+
type: "object",
|
|
245
|
+
properties: {
|
|
246
|
+
yaml: {
|
|
247
|
+
type: "string",
|
|
248
|
+
description:
|
|
249
|
+
"The Maestro YAML flow content. MUST use tapOn before inputText for each field!",
|
|
250
|
+
},
|
|
251
|
+
name: {
|
|
252
|
+
type: "string",
|
|
253
|
+
description:
|
|
254
|
+
"Name for this test (used for reporting and screenshots)",
|
|
255
|
+
},
|
|
256
|
+
retries: {
|
|
257
|
+
type: "number",
|
|
258
|
+
description:
|
|
259
|
+
"Optional: Number of retries if test fails (default: from config or 0)",
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
required: ["yaml", "name"],
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: "run_test_suite",
|
|
267
|
+
description:
|
|
268
|
+
"Run multiple Maestro tests in sequence. Each YAML must follow the rules: appId at top, clearState, launchApp, and ALWAYS tapOn before inputText!",
|
|
269
|
+
inputSchema: {
|
|
270
|
+
type: "object",
|
|
271
|
+
properties: {
|
|
272
|
+
tests: {
|
|
273
|
+
type: "array",
|
|
274
|
+
items: {
|
|
275
|
+
type: "object",
|
|
276
|
+
properties: {
|
|
277
|
+
yaml: {
|
|
278
|
+
type: "string",
|
|
279
|
+
description:
|
|
280
|
+
"The Maestro YAML. MUST use tapOn before inputText!",
|
|
281
|
+
},
|
|
282
|
+
name: {
|
|
283
|
+
type: "string",
|
|
284
|
+
description: "Name for this test",
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
required: ["yaml", "name"],
|
|
288
|
+
},
|
|
289
|
+
description: "Array of tests to run",
|
|
290
|
+
},
|
|
291
|
+
retries: {
|
|
292
|
+
type: "number",
|
|
293
|
+
description:
|
|
294
|
+
"Optional: Number of retries for failed tests (default: from config or 0)",
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
required: ["tests"],
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
// === Results & Reporting Tools ===
|
|
302
|
+
{
|
|
303
|
+
name: "run_tests_with_report",
|
|
304
|
+
description:
|
|
305
|
+
"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.",
|
|
306
|
+
inputSchema: {
|
|
307
|
+
type: "object",
|
|
308
|
+
properties: {
|
|
309
|
+
tests: {
|
|
310
|
+
type: "array",
|
|
311
|
+
items: {
|
|
312
|
+
type: "object",
|
|
313
|
+
properties: {
|
|
314
|
+
yaml: {
|
|
315
|
+
type: "string",
|
|
316
|
+
description:
|
|
317
|
+
"The Maestro YAML. MUST use tapOn before inputText!",
|
|
318
|
+
},
|
|
319
|
+
name: {
|
|
320
|
+
type: "string",
|
|
321
|
+
description: "Name for this test",
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
required: ["yaml", "name"],
|
|
325
|
+
},
|
|
326
|
+
description: "Array of tests to run",
|
|
327
|
+
},
|
|
328
|
+
promptFile: {
|
|
329
|
+
type: "string",
|
|
330
|
+
description: "Name of the prompt file (for report metadata)",
|
|
331
|
+
},
|
|
332
|
+
appId: {
|
|
333
|
+
type: "string",
|
|
334
|
+
description: "App ID (for report metadata)",
|
|
335
|
+
},
|
|
336
|
+
retries: {
|
|
337
|
+
type: "number",
|
|
338
|
+
description: "Number of retries for failed tests",
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
required: ["tests"],
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
name: "generate_report",
|
|
346
|
+
description:
|
|
347
|
+
"Generate HTML and JSON report from test results. Call this after running tests to create a visual summary report.",
|
|
348
|
+
inputSchema: {
|
|
349
|
+
type: "object",
|
|
350
|
+
properties: {
|
|
351
|
+
results: {
|
|
352
|
+
type: "array",
|
|
353
|
+
items: {
|
|
354
|
+
type: "object",
|
|
355
|
+
properties: {
|
|
356
|
+
name: { type: "string", description: "Test name" },
|
|
357
|
+
success: {
|
|
358
|
+
type: "boolean",
|
|
359
|
+
description: "Whether the test passed",
|
|
360
|
+
},
|
|
361
|
+
duration: {
|
|
362
|
+
type: "number",
|
|
363
|
+
description: "Test duration in milliseconds",
|
|
364
|
+
},
|
|
365
|
+
error: {
|
|
366
|
+
type: "string",
|
|
367
|
+
description: "Error message if test failed",
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
required: ["name", "success"],
|
|
371
|
+
},
|
|
372
|
+
description:
|
|
373
|
+
"Array of test results with name, success, duration, error fields",
|
|
374
|
+
},
|
|
375
|
+
promptFile: {
|
|
376
|
+
type: "string",
|
|
377
|
+
description: "Name of the prompt file",
|
|
378
|
+
},
|
|
379
|
+
appId: {
|
|
380
|
+
type: "string",
|
|
381
|
+
description: "App ID",
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
required: ["results"],
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
name: "list_reports",
|
|
389
|
+
description:
|
|
390
|
+
"List all generated test reports. Returns paths to HTML and JSON report files.",
|
|
391
|
+
inputSchema: {
|
|
392
|
+
type: "object",
|
|
393
|
+
properties: {},
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
name: "get_test_results",
|
|
398
|
+
description:
|
|
399
|
+
"Get the results from the last test run or a specific run by ID.",
|
|
400
|
+
inputSchema: {
|
|
401
|
+
type: "object",
|
|
402
|
+
properties: {
|
|
403
|
+
runId: {
|
|
404
|
+
type: "string",
|
|
405
|
+
description: "Optional: Specific run ID to get results for",
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
name: "take_screenshot",
|
|
412
|
+
description:
|
|
413
|
+
"Take a screenshot of the current device screen. Useful for debugging or verification.",
|
|
414
|
+
inputSchema: {
|
|
415
|
+
type: "object",
|
|
416
|
+
properties: {
|
|
417
|
+
name: {
|
|
418
|
+
type: "string",
|
|
419
|
+
description: "Name for the screenshot file",
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
required: ["name"],
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
name: "cleanup_results",
|
|
427
|
+
description:
|
|
428
|
+
"Clean up old test results and screenshots to free up disk space. Keeps the most recent results.",
|
|
429
|
+
inputSchema: {
|
|
430
|
+
type: "object",
|
|
431
|
+
properties: {
|
|
432
|
+
keepLast: {
|
|
433
|
+
type: "number",
|
|
434
|
+
description: "Number of recent results to keep (default: 50)",
|
|
435
|
+
},
|
|
436
|
+
deleteScreenshots: {
|
|
437
|
+
type: "boolean",
|
|
438
|
+
description: "Whether to delete old screenshots (default: true)",
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
|
|
444
|
+
// === App Context/Training Tools ===
|
|
445
|
+
{
|
|
446
|
+
name: "register_elements",
|
|
447
|
+
description:
|
|
448
|
+
"Register UI elements for an app to help AI generate better YAML. Provide testIDs, accessibilityLabels, and text values for app elements. This teaches the AI about your app's UI structure.",
|
|
449
|
+
inputSchema: {
|
|
450
|
+
type: "object",
|
|
451
|
+
properties: {
|
|
452
|
+
appId: {
|
|
453
|
+
type: "string",
|
|
454
|
+
description: "App package ID (e.g., 'com.myapp')",
|
|
455
|
+
},
|
|
456
|
+
elements: {
|
|
457
|
+
type: "object",
|
|
458
|
+
description:
|
|
459
|
+
"Object containing element definitions. Each key is the element name, value contains: testId, accessibilityLabel, text, type, description",
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
required: ["appId", "elements"],
|
|
463
|
+
},
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
name: "register_screen",
|
|
467
|
+
description:
|
|
468
|
+
"Register a screen structure for an app. Define what elements and actions are available on each screen.",
|
|
469
|
+
inputSchema: {
|
|
470
|
+
type: "object",
|
|
471
|
+
properties: {
|
|
472
|
+
appId: {
|
|
473
|
+
type: "string",
|
|
474
|
+
description: "App package ID",
|
|
475
|
+
},
|
|
476
|
+
screenName: {
|
|
477
|
+
type: "string",
|
|
478
|
+
description: "Name of the screen (e.g., 'LoginScreen', 'Dashboard')",
|
|
479
|
+
},
|
|
480
|
+
screenData: {
|
|
481
|
+
type: "object",
|
|
482
|
+
description:
|
|
483
|
+
"Screen data including: description, elements (array of element names), actions (array of possible actions)",
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
required: ["appId", "screenName", "screenData"],
|
|
487
|
+
},
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
name: "save_successful_flow",
|
|
491
|
+
description:
|
|
492
|
+
"Save a successful test flow as a pattern for future reference. Call this after a test passes to help AI learn from successful patterns.",
|
|
493
|
+
inputSchema: {
|
|
494
|
+
type: "object",
|
|
495
|
+
properties: {
|
|
496
|
+
appId: {
|
|
497
|
+
type: "string",
|
|
498
|
+
description: "App package ID",
|
|
499
|
+
},
|
|
500
|
+
flowName: {
|
|
501
|
+
type: "string",
|
|
502
|
+
description: "Name for this flow pattern",
|
|
503
|
+
},
|
|
504
|
+
yamlContent: {
|
|
505
|
+
type: "string",
|
|
506
|
+
description: "The successful Maestro YAML content",
|
|
507
|
+
},
|
|
508
|
+
description: {
|
|
509
|
+
type: "string",
|
|
510
|
+
description: "Optional: Description of what this flow does",
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
required: ["appId", "flowName", "yamlContent"],
|
|
514
|
+
},
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
name: "get_saved_flows",
|
|
518
|
+
description:
|
|
519
|
+
"Get all saved successful flows for an app. Use these as patterns when generating new tests.",
|
|
520
|
+
inputSchema: {
|
|
521
|
+
type: "object",
|
|
522
|
+
properties: {
|
|
523
|
+
appId: {
|
|
524
|
+
type: "string",
|
|
525
|
+
description: "App package ID",
|
|
526
|
+
},
|
|
527
|
+
},
|
|
528
|
+
required: ["appId"],
|
|
529
|
+
},
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
name: "delete_flow",
|
|
533
|
+
description: "Delete a saved flow pattern.",
|
|
534
|
+
inputSchema: {
|
|
535
|
+
type: "object",
|
|
536
|
+
properties: {
|
|
537
|
+
appId: {
|
|
538
|
+
type: "string",
|
|
539
|
+
description: "App package ID",
|
|
540
|
+
},
|
|
541
|
+
flowName: {
|
|
542
|
+
type: "string",
|
|
543
|
+
description: "Name of the flow to delete",
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
required: ["appId", "flowName"],
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
name: "get_ai_context",
|
|
551
|
+
description:
|
|
552
|
+
"Get the formatted AI context for an app. This returns all registered elements, screens, and example flows in a format optimized for AI consumption. ALWAYS call this before generating Maestro YAML to get app-specific information.",
|
|
553
|
+
inputSchema: {
|
|
554
|
+
type: "object",
|
|
555
|
+
properties: {
|
|
556
|
+
appId: {
|
|
557
|
+
type: "string",
|
|
558
|
+
description: "App package ID",
|
|
559
|
+
},
|
|
560
|
+
},
|
|
561
|
+
required: ["appId"],
|
|
562
|
+
},
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
name: "get_full_context",
|
|
566
|
+
description:
|
|
567
|
+
"Get the complete raw app context including all elements, screens, and flows.",
|
|
568
|
+
inputSchema: {
|
|
569
|
+
type: "object",
|
|
570
|
+
properties: {
|
|
571
|
+
appId: {
|
|
572
|
+
type: "string",
|
|
573
|
+
description: "App package ID",
|
|
574
|
+
},
|
|
575
|
+
},
|
|
576
|
+
required: ["appId"],
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
name: "clear_app_context",
|
|
581
|
+
description:
|
|
582
|
+
"Clear all saved context for an app (elements, screens, flows).",
|
|
583
|
+
inputSchema: {
|
|
584
|
+
type: "object",
|
|
585
|
+
properties: {
|
|
586
|
+
appId: {
|
|
587
|
+
type: "string",
|
|
588
|
+
description: "App package ID",
|
|
589
|
+
},
|
|
590
|
+
},
|
|
591
|
+
required: ["appId"],
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
name: "list_app_contexts",
|
|
596
|
+
description: "List all apps that have saved context data.",
|
|
597
|
+
inputSchema: {
|
|
598
|
+
type: "object",
|
|
599
|
+
properties: {},
|
|
600
|
+
},
|
|
601
|
+
},
|
|
602
|
+
|
|
603
|
+
// === YAML Generation Tools (CRITICAL) ===
|
|
604
|
+
{
|
|
605
|
+
name: "get_yaml_instructions",
|
|
606
|
+
description:
|
|
607
|
+
"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.",
|
|
608
|
+
inputSchema: {
|
|
609
|
+
type: "object",
|
|
610
|
+
properties: {
|
|
611
|
+
appId: {
|
|
612
|
+
type: "string",
|
|
613
|
+
description: "App package ID to get app-specific context",
|
|
614
|
+
},
|
|
615
|
+
},
|
|
616
|
+
},
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
name: "validate_yaml_structure",
|
|
620
|
+
description:
|
|
621
|
+
"Validate YAML structure before running a test. Checks for common issues like missing 'tapOn' before 'inputText' which causes text to go to wrong fields.",
|
|
622
|
+
inputSchema: {
|
|
623
|
+
type: "object",
|
|
624
|
+
properties: {
|
|
625
|
+
yamlContent: {
|
|
626
|
+
type: "string",
|
|
627
|
+
description: "The Maestro YAML content to validate",
|
|
628
|
+
},
|
|
629
|
+
},
|
|
630
|
+
required: ["yamlContent"],
|
|
631
|
+
},
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
name: "get_test_pattern",
|
|
635
|
+
description:
|
|
636
|
+
"Get a standard test pattern template. Available: login, form, search, navigation, list, settings, logout. Use these as starting points.",
|
|
637
|
+
inputSchema: {
|
|
638
|
+
type: "object",
|
|
639
|
+
properties: {
|
|
640
|
+
patternName: {
|
|
641
|
+
type: "string",
|
|
642
|
+
description:
|
|
643
|
+
"Pattern name: login, form, search, navigation, list, settings, or logout",
|
|
644
|
+
enum: [
|
|
645
|
+
"login",
|
|
646
|
+
"form",
|
|
647
|
+
"search",
|
|
648
|
+
"navigation",
|
|
649
|
+
"list",
|
|
650
|
+
"settings",
|
|
651
|
+
"logout",
|
|
652
|
+
],
|
|
653
|
+
},
|
|
654
|
+
},
|
|
655
|
+
required: ["patternName"],
|
|
656
|
+
},
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
name: "get_screen_analysis_help",
|
|
660
|
+
description:
|
|
661
|
+
"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.",
|
|
662
|
+
inputSchema: {
|
|
663
|
+
type: "object",
|
|
664
|
+
properties: {},
|
|
665
|
+
},
|
|
666
|
+
},
|
|
667
|
+
];
|
|
668
|
+
|
|
669
|
+
// ============================================
|
|
670
|
+
// HANDLERS
|
|
671
|
+
// ============================================
|
|
672
|
+
|
|
673
|
+
// List available tools
|
|
674
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
675
|
+
logger.info("Listing available tools");
|
|
676
|
+
return { tools: TOOLS };
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
// ============================================
|
|
680
|
+
// VALIDATION MIDDLEWARE
|
|
681
|
+
// ============================================
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Create a validation error response
|
|
685
|
+
*/
|
|
686
|
+
function createValidationErrorResponse(toolName, validationResult) {
|
|
687
|
+
logSecurityEvent("TOOL_VALIDATION_FAILED", {
|
|
688
|
+
tool: toolName,
|
|
689
|
+
errors: validationResult.errors,
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
return {
|
|
693
|
+
content: [
|
|
694
|
+
{
|
|
695
|
+
type: "text",
|
|
696
|
+
text: JSON.stringify({
|
|
697
|
+
success: false,
|
|
698
|
+
error: "Input validation failed",
|
|
699
|
+
validationErrors: validationResult.errors,
|
|
700
|
+
message: validationResult.message,
|
|
701
|
+
hint: "Please check the input parameters and try again.",
|
|
702
|
+
}),
|
|
703
|
+
},
|
|
704
|
+
],
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Create a security error response
|
|
710
|
+
*/
|
|
711
|
+
function createSecurityErrorResponse(error) {
|
|
712
|
+
return {
|
|
713
|
+
content: [
|
|
714
|
+
{
|
|
715
|
+
type: "text",
|
|
716
|
+
text: JSON.stringify({
|
|
717
|
+
success: false,
|
|
718
|
+
error: "Security violation",
|
|
719
|
+
code: error.code,
|
|
720
|
+
message: error.message,
|
|
721
|
+
details: error.details,
|
|
722
|
+
}),
|
|
723
|
+
},
|
|
724
|
+
],
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// Handle tool calls
|
|
729
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
730
|
+
const { name, arguments: args } = request.params;
|
|
731
|
+
const startTime = Date.now();
|
|
732
|
+
|
|
733
|
+
logger.info(`Tool called: ${name}`, { args });
|
|
734
|
+
|
|
735
|
+
// ============================================
|
|
736
|
+
// STEP 1: Validate input with Zod schema
|
|
737
|
+
// ============================================
|
|
738
|
+
const validationResult = validateToolInput(name, args);
|
|
739
|
+
|
|
740
|
+
if (!validationResult.success) {
|
|
741
|
+
logger.warn(`Validation failed for tool: ${name}`, {
|
|
742
|
+
errors: validationResult.errors,
|
|
743
|
+
});
|
|
744
|
+
return createValidationErrorResponse(name, validationResult);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// Use validated/sanitized data
|
|
748
|
+
const validatedArgs = validationResult.data;
|
|
749
|
+
|
|
750
|
+
// ============================================
|
|
751
|
+
// STEP 2: Log security event for audit trail
|
|
752
|
+
// ============================================
|
|
753
|
+
logSecurityEvent("TOOL_EXECUTION_START", {
|
|
754
|
+
tool: name,
|
|
755
|
+
safeMode: isSafeModeEnabled(),
|
|
756
|
+
hasArgs: Object.keys(validatedArgs || {}).length > 0,
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
try {
|
|
760
|
+
// ============================================
|
|
761
|
+
// STEP 3: Execute tool with validated args
|
|
762
|
+
// ============================================
|
|
763
|
+
let result;
|
|
764
|
+
|
|
765
|
+
switch (name) {
|
|
766
|
+
// Prompt tools
|
|
767
|
+
case "read_prompt_file":
|
|
768
|
+
result = await readPromptFile(validatedArgs.file);
|
|
769
|
+
break;
|
|
770
|
+
|
|
771
|
+
case "list_prompt_files":
|
|
772
|
+
result = await listPromptFiles(validatedArgs.directory);
|
|
773
|
+
break;
|
|
774
|
+
|
|
775
|
+
// Device management tools
|
|
776
|
+
case "list_devices":
|
|
777
|
+
result = await listDevices();
|
|
778
|
+
break;
|
|
779
|
+
|
|
780
|
+
case "select_device":
|
|
781
|
+
result = await selectDevice(validatedArgs.deviceId);
|
|
782
|
+
break;
|
|
783
|
+
|
|
784
|
+
case "clear_device":
|
|
785
|
+
result = await clearDevice();
|
|
786
|
+
break;
|
|
787
|
+
|
|
788
|
+
case "check_device":
|
|
789
|
+
result = await checkDevice();
|
|
790
|
+
break;
|
|
791
|
+
|
|
792
|
+
case "check_app":
|
|
793
|
+
result = await checkApp(validatedArgs.appId);
|
|
794
|
+
break;
|
|
795
|
+
|
|
796
|
+
// Config tools
|
|
797
|
+
case "get_app_config":
|
|
798
|
+
result = await getAppConfig();
|
|
799
|
+
break;
|
|
800
|
+
|
|
801
|
+
// Validation tools
|
|
802
|
+
case "validate_maestro_yaml":
|
|
803
|
+
result = await validateMaestroYaml(validatedArgs.yaml);
|
|
804
|
+
break;
|
|
805
|
+
|
|
806
|
+
// Execution tools
|
|
807
|
+
case "run_test":
|
|
808
|
+
result = await runTest(validatedArgs.yaml, validatedArgs.name, {
|
|
809
|
+
retries: validatedArgs.retries,
|
|
810
|
+
});
|
|
811
|
+
break;
|
|
812
|
+
|
|
813
|
+
case "run_test_suite":
|
|
814
|
+
result = await runTestSuite(validatedArgs.tests, {
|
|
815
|
+
retries: validatedArgs.retries,
|
|
816
|
+
});
|
|
817
|
+
break;
|
|
818
|
+
|
|
819
|
+
// Results & reporting tools
|
|
820
|
+
case "run_tests_with_report":
|
|
821
|
+
result = await runTestSuiteWithReport(validatedArgs.tests, {
|
|
822
|
+
promptFile: validatedArgs.promptFile,
|
|
823
|
+
appId: validatedArgs.appId,
|
|
824
|
+
retries: validatedArgs.retries,
|
|
825
|
+
});
|
|
826
|
+
break;
|
|
827
|
+
|
|
828
|
+
case "generate_report":
|
|
829
|
+
result = await generateTestReport(validatedArgs.results, {
|
|
830
|
+
promptFile: validatedArgs.promptFile,
|
|
831
|
+
appId: validatedArgs.appId,
|
|
832
|
+
});
|
|
833
|
+
break;
|
|
834
|
+
|
|
835
|
+
case "list_reports":
|
|
836
|
+
result = await listTestReports();
|
|
837
|
+
break;
|
|
838
|
+
|
|
839
|
+
case "get_test_results":
|
|
840
|
+
result = await getTestResults(validatedArgs.runId);
|
|
841
|
+
break;
|
|
842
|
+
|
|
843
|
+
case "take_screenshot":
|
|
844
|
+
result = await takeScreenshot(validatedArgs.name);
|
|
845
|
+
break;
|
|
846
|
+
|
|
847
|
+
case "cleanup_results":
|
|
848
|
+
result = await cleanupResults({
|
|
849
|
+
keepLast: validatedArgs.keepLast,
|
|
850
|
+
deleteScreenshots: validatedArgs.deleteScreenshots,
|
|
851
|
+
});
|
|
852
|
+
break;
|
|
853
|
+
|
|
854
|
+
// App context/training tools
|
|
855
|
+
case "register_elements":
|
|
856
|
+
result = await registerAppElements(
|
|
857
|
+
validatedArgs.appId,
|
|
858
|
+
validatedArgs.elements
|
|
859
|
+
);
|
|
860
|
+
break;
|
|
861
|
+
|
|
862
|
+
case "register_screen":
|
|
863
|
+
result = await registerAppScreen(
|
|
864
|
+
validatedArgs.appId,
|
|
865
|
+
validatedArgs.screenName,
|
|
866
|
+
validatedArgs.screenData
|
|
867
|
+
);
|
|
868
|
+
break;
|
|
869
|
+
|
|
870
|
+
case "save_successful_flow":
|
|
871
|
+
result = await saveFlow(
|
|
872
|
+
validatedArgs.appId,
|
|
873
|
+
validatedArgs.flowName,
|
|
874
|
+
validatedArgs.yamlContent,
|
|
875
|
+
validatedArgs.description
|
|
876
|
+
);
|
|
877
|
+
break;
|
|
878
|
+
|
|
879
|
+
case "get_saved_flows":
|
|
880
|
+
result = await getFlows(validatedArgs.appId);
|
|
881
|
+
break;
|
|
882
|
+
|
|
883
|
+
case "delete_flow":
|
|
884
|
+
result = await removeFlow(validatedArgs.appId, validatedArgs.flowName);
|
|
885
|
+
break;
|
|
886
|
+
|
|
887
|
+
case "get_ai_context":
|
|
888
|
+
result = await getAIContext(validatedArgs.appId);
|
|
889
|
+
break;
|
|
890
|
+
|
|
891
|
+
case "get_full_context":
|
|
892
|
+
result = await getAppContext(validatedArgs.appId);
|
|
893
|
+
break;
|
|
894
|
+
|
|
895
|
+
case "clear_app_context":
|
|
896
|
+
result = await clearContext(validatedArgs.appId);
|
|
897
|
+
break;
|
|
898
|
+
|
|
899
|
+
case "list_app_contexts":
|
|
900
|
+
result = await listContexts();
|
|
901
|
+
break;
|
|
902
|
+
|
|
903
|
+
// YAML generation tools
|
|
904
|
+
case "get_yaml_instructions":
|
|
905
|
+
result = await getYamlInstructions(validatedArgs.appId);
|
|
906
|
+
break;
|
|
907
|
+
|
|
908
|
+
case "validate_yaml_structure":
|
|
909
|
+
result = await validateYamlBeforeRun(validatedArgs.yamlContent);
|
|
910
|
+
break;
|
|
911
|
+
|
|
912
|
+
case "get_test_pattern":
|
|
913
|
+
result = await getTestPattern(validatedArgs.patternName);
|
|
914
|
+
break;
|
|
915
|
+
|
|
916
|
+
case "get_screen_analysis_help":
|
|
917
|
+
result = await getScreenAnalysis();
|
|
918
|
+
break;
|
|
919
|
+
|
|
920
|
+
default:
|
|
921
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// ============================================
|
|
925
|
+
// STEP 4: Log successful execution
|
|
926
|
+
// ============================================
|
|
927
|
+
const duration = Date.now() - startTime;
|
|
928
|
+
logSecurityEvent("TOOL_EXECUTION_SUCCESS", {
|
|
929
|
+
tool: name,
|
|
930
|
+
duration: `${duration}ms`,
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
return result;
|
|
934
|
+
} catch (error) {
|
|
935
|
+
// ============================================
|
|
936
|
+
// ERROR HANDLING
|
|
937
|
+
// ============================================
|
|
938
|
+
const duration = Date.now() - startTime;
|
|
939
|
+
|
|
940
|
+
// Handle SecurityError specifically
|
|
941
|
+
if (error instanceof SecurityError) {
|
|
942
|
+
logger.error(`Security error in tool: ${name}`, {
|
|
943
|
+
code: error.code,
|
|
944
|
+
message: error.message,
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
logSecurityEvent("TOOL_SECURITY_ERROR", {
|
|
948
|
+
tool: name,
|
|
949
|
+
code: error.code,
|
|
950
|
+
duration: `${duration}ms`,
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
return createSecurityErrorResponse(error);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// Handle general errors
|
|
957
|
+
logger.error(`Tool error: ${name}`, { error: error.message });
|
|
958
|
+
|
|
959
|
+
logSecurityEvent("TOOL_EXECUTION_ERROR", {
|
|
960
|
+
tool: name,
|
|
961
|
+
error: error.message,
|
|
962
|
+
duration: `${duration}ms`,
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
return {
|
|
966
|
+
content: [
|
|
967
|
+
{
|
|
968
|
+
type: "text",
|
|
969
|
+
text: JSON.stringify({
|
|
970
|
+
success: false,
|
|
971
|
+
error: error.message,
|
|
972
|
+
}),
|
|
973
|
+
},
|
|
974
|
+
],
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
// List available resources (prompt files)
|
|
980
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
981
|
+
const result = await listPromptFiles("prompts");
|
|
982
|
+
const files = JSON.parse(result.content[0].text).files || [];
|
|
983
|
+
|
|
984
|
+
return {
|
|
985
|
+
resources: files.map((file) => ({
|
|
986
|
+
uri: `prompts://${file}`,
|
|
987
|
+
name: file,
|
|
988
|
+
mimeType: "text/plain",
|
|
989
|
+
description: `Prompt file: ${file}`,
|
|
990
|
+
})),
|
|
991
|
+
};
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
// Read a resource
|
|
995
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
996
|
+
const uri = request.params.uri;
|
|
997
|
+
const file = uri.replace("prompts://", "");
|
|
998
|
+
const result = await readPromptFile(`prompts/${file}`);
|
|
999
|
+
|
|
1000
|
+
return {
|
|
1001
|
+
contents: [
|
|
1002
|
+
{
|
|
1003
|
+
uri,
|
|
1004
|
+
mimeType: "text/plain",
|
|
1005
|
+
text: result.content[0].text,
|
|
1006
|
+
},
|
|
1007
|
+
],
|
|
1008
|
+
};
|
|
1009
|
+
});
|
|
1010
|
+
|
|
1011
|
+
// ============================================
|
|
1012
|
+
// START SERVER
|
|
1013
|
+
// ============================================
|
|
1014
|
+
|
|
1015
|
+
async function main() {
|
|
1016
|
+
logger.info(`Starting MCP Maestro Mobile AI v${SERVER_VERSION}...`);
|
|
1017
|
+
logger.info("");
|
|
1018
|
+
|
|
1019
|
+
// Log security configuration
|
|
1020
|
+
const securityConfig = getSecurityConfig();
|
|
1021
|
+
logger.info("Security Configuration:", securityConfig);
|
|
1022
|
+
logger.info(
|
|
1023
|
+
` Safe Mode: ${securityConfig.safeMode ? "ENABLED ✓" : "DISABLED ⚠"}`
|
|
1024
|
+
);
|
|
1025
|
+
logger.info(` Security Mode: ${securityConfig.mode}`);
|
|
1026
|
+
logger.info(
|
|
1027
|
+
` Security Logging: ${
|
|
1028
|
+
securityConfig.logSecurityEvents ? "ENABLED" : "DISABLED"
|
|
1029
|
+
}`
|
|
1030
|
+
);
|
|
1031
|
+
logger.info("");
|
|
1032
|
+
|
|
1033
|
+
// Validate prerequisites before starting
|
|
1034
|
+
// This will exit with code 2 if critical prerequisites are missing
|
|
1035
|
+
await validatePrerequisites({
|
|
1036
|
+
exitOnError: true,
|
|
1037
|
+
checkDevice: false, // Don't require device at startup
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
logger.info("");
|
|
1041
|
+
logger.info("Prerequisites validated. Starting server...");
|
|
1042
|
+
|
|
1043
|
+
const transport = new StdioServerTransport();
|
|
1044
|
+
await server.connect(transport);
|
|
1045
|
+
|
|
1046
|
+
logger.info(
|
|
1047
|
+
`MCP Maestro Mobile AI server v${SERVER_VERSION} running on stdio`
|
|
1048
|
+
);
|
|
1049
|
+
|
|
1050
|
+
logSecurityEvent("SERVER_STARTED", {
|
|
1051
|
+
version: SERVER_VERSION,
|
|
1052
|
+
safeMode: securityConfig.safeMode,
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
main().catch((error) => {
|
|
1057
|
+
logger.error("Failed to start server", { error: error.message });
|
|
1058
|
+
process.exit(1);
|
|
1059
|
+
});
|