mcp-maestro-mobile-ai 1.4.0 → 1.6.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 +101 -1
- package/package.json +6 -3
- package/src/mcp-server/index.js +339 -4
- package/src/mcp-server/schemas/toolSchemas.js +184 -0
- package/src/mcp-server/tools/contextTools.js +309 -2
- package/src/mcp-server/tools/runTools.js +409 -31
- package/src/mcp-server/utils/knownIssues.js +564 -0
- package/src/mcp-server/utils/promptAnalyzer.js +701 -0
- package/src/mcp-server/utils/yamlCache.js +381 -0
- package/src/mcp-server/utils/yamlGenerator.js +426 -0
- package/src/mcp-server/utils/yamlTemplate.js +303 -0
|
@@ -11,6 +11,15 @@ import { runMaestroFlow, checkDeviceConnection, checkAppInstalled, getConfig } f
|
|
|
11
11
|
import { validateMaestroYaml } from './validateTools.js';
|
|
12
12
|
import { validateYamlStructure } from '../utils/yamlTemplate.js';
|
|
13
13
|
import { generateReport, getReportsDir, listReports } from '../utils/reportGenerator.js';
|
|
14
|
+
import {
|
|
15
|
+
lookupCache,
|
|
16
|
+
saveToCache,
|
|
17
|
+
updateCacheUsage,
|
|
18
|
+
listCache as listYamlCache,
|
|
19
|
+
clearCache as clearYamlCache,
|
|
20
|
+
getCacheStats,
|
|
21
|
+
deleteFromCache,
|
|
22
|
+
} from '../utils/yamlCache.js';
|
|
14
23
|
|
|
15
24
|
const __filename = fileURLToPath(import.meta.url);
|
|
16
25
|
const __dirname = dirname(__filename);
|
|
@@ -160,20 +169,55 @@ Please regenerate the YAML following these rules.
|
|
|
160
169
|
|
|
161
170
|
logger.info(`Test ${result.success ? 'passed' : 'failed'}: ${testName}`);
|
|
162
171
|
|
|
172
|
+
// Build response
|
|
173
|
+
const response = {
|
|
174
|
+
success: result.success,
|
|
175
|
+
name: testName,
|
|
176
|
+
duration: result.duration,
|
|
177
|
+
attempts: result.attempts || 1,
|
|
178
|
+
error: result.error || null,
|
|
179
|
+
screenshot: result.screenshot || null,
|
|
180
|
+
output: result.output ? result.output.substring(0, 500) : null,
|
|
181
|
+
runId,
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// If test passed and we have a prompt, automatically save to cache
|
|
185
|
+
if (result.success && options.prompt && !options.fromCache) {
|
|
186
|
+
try {
|
|
187
|
+
const cacheResult = await saveToCache(
|
|
188
|
+
options.prompt,
|
|
189
|
+
yamlContent,
|
|
190
|
+
testName,
|
|
191
|
+
appId
|
|
192
|
+
);
|
|
193
|
+
if (cacheResult.success) {
|
|
194
|
+
response.cached = true;
|
|
195
|
+
response.cacheHash = cacheResult.hash;
|
|
196
|
+
response.cacheMessage = '✅ Test passed! YAML automatically saved to cache for faster future execution.';
|
|
197
|
+
logger.info(`Auto-cached successful test: ${testName} (hash: ${cacheResult.hash})`);
|
|
198
|
+
}
|
|
199
|
+
} catch (cacheError) {
|
|
200
|
+
logger.warn(`Failed to auto-cache test: ${testName}`, { error: cacheError.message });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// If this was from cache, include that info
|
|
205
|
+
if (options.fromCache) {
|
|
206
|
+
response.fromCache = true;
|
|
207
|
+
response.cacheMessage = '⚡ Executed from cached YAML (faster execution)';
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// If test failed, indicate it was not cached
|
|
211
|
+
if (!result.success) {
|
|
212
|
+
response.cached = false;
|
|
213
|
+
response.cacheMessage = '❌ Test failed - not saved to cache. Fix the issue and re-run to cache.';
|
|
214
|
+
}
|
|
215
|
+
|
|
163
216
|
return {
|
|
164
217
|
content: [
|
|
165
218
|
{
|
|
166
219
|
type: 'text',
|
|
167
|
-
text: JSON.stringify(
|
|
168
|
-
success: result.success,
|
|
169
|
-
name: testName,
|
|
170
|
-
duration: result.duration,
|
|
171
|
-
attempts: result.attempts || 1,
|
|
172
|
-
error: result.error || null,
|
|
173
|
-
screenshot: result.screenshot || null,
|
|
174
|
-
output: result.output ? result.output.substring(0, 500) : null,
|
|
175
|
-
runId,
|
|
176
|
-
}),
|
|
220
|
+
text: JSON.stringify(response),
|
|
177
221
|
},
|
|
178
222
|
],
|
|
179
223
|
};
|
|
@@ -526,37 +570,363 @@ export async function runTestSuiteWithReport(tests, options = {}) {
|
|
|
526
570
|
const passed = results.filter(r => r.success).length;
|
|
527
571
|
const failed = results.length - passed;
|
|
528
572
|
|
|
573
|
+
// Auto-cache successful tests
|
|
574
|
+
const cachedTests = [];
|
|
575
|
+
for (let i = 0; i < results.length; i++) {
|
|
576
|
+
const result = results[i];
|
|
577
|
+
const test = tests[i];
|
|
578
|
+
|
|
579
|
+
if (result.success && test.prompt) {
|
|
580
|
+
try {
|
|
581
|
+
const cacheResult = await saveToCache(
|
|
582
|
+
test.prompt,
|
|
583
|
+
test.yaml,
|
|
584
|
+
test.name,
|
|
585
|
+
options.appId
|
|
586
|
+
);
|
|
587
|
+
if (cacheResult.success) {
|
|
588
|
+
cachedTests.push({
|
|
589
|
+
name: test.name,
|
|
590
|
+
hash: cacheResult.hash,
|
|
591
|
+
});
|
|
592
|
+
logger.info(`Auto-cached successful test: ${test.name} (hash: ${cacheResult.hash})`);
|
|
593
|
+
}
|
|
594
|
+
} catch (cacheError) {
|
|
595
|
+
logger.warn(`Failed to auto-cache test: ${test.name}`, { error: cacheError.message });
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Build response
|
|
601
|
+
const response = {
|
|
602
|
+
success: failed === 0,
|
|
603
|
+
totalDuration: `${totalDuration}s`,
|
|
604
|
+
summary: {
|
|
605
|
+
total: results.length,
|
|
606
|
+
passed,
|
|
607
|
+
failed,
|
|
608
|
+
passRate: reportResult.summary.passRate,
|
|
609
|
+
},
|
|
610
|
+
report: {
|
|
611
|
+
reportId: reportResult.reportId,
|
|
612
|
+
htmlPath: reportResult.htmlPath,
|
|
613
|
+
jsonPath: reportResult.jsonPath,
|
|
614
|
+
message: `📊 Report generated: ${reportResult.htmlPath}`,
|
|
615
|
+
},
|
|
616
|
+
tests: results.map(r => ({
|
|
617
|
+
name: r.name,
|
|
618
|
+
success: r.success,
|
|
619
|
+
duration: r.duration,
|
|
620
|
+
error: r.error || null,
|
|
621
|
+
})),
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
// Add cache info
|
|
625
|
+
if (cachedTests.length > 0) {
|
|
626
|
+
response.cached = {
|
|
627
|
+
count: cachedTests.length,
|
|
628
|
+
tests: cachedTests,
|
|
629
|
+
message: `✅ ${cachedTests.length} successful test(s) automatically saved to cache for faster future execution.`,
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (failed > 0) {
|
|
634
|
+
response.notCached = {
|
|
635
|
+
count: failed,
|
|
636
|
+
message: `❌ ${failed} failed test(s) not saved to cache. Fix issues and re-run to cache.`,
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
return {
|
|
641
|
+
content: [
|
|
642
|
+
{
|
|
643
|
+
type: 'text',
|
|
644
|
+
text: JSON.stringify(response),
|
|
645
|
+
},
|
|
646
|
+
],
|
|
647
|
+
};
|
|
648
|
+
} catch (error) {
|
|
649
|
+
logger.error('Test suite with report error', { error: error.message });
|
|
529
650
|
return {
|
|
530
651
|
content: [
|
|
531
652
|
{
|
|
532
653
|
type: 'text',
|
|
533
654
|
text: JSON.stringify({
|
|
534
|
-
success:
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
655
|
+
success: false,
|
|
656
|
+
error: error.message,
|
|
657
|
+
}),
|
|
658
|
+
},
|
|
659
|
+
],
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Run a test from cache or generate new
|
|
666
|
+
* Checks cache first, falls back to provided YAML
|
|
667
|
+
*/
|
|
668
|
+
export async function runTestWithCache(prompt, yamlContent, testName, options = {}) {
|
|
669
|
+
const appId = options.appId || null;
|
|
670
|
+
|
|
671
|
+
// Check cache first
|
|
672
|
+
const cached = await lookupCache(prompt, appId);
|
|
673
|
+
|
|
674
|
+
if (cached) {
|
|
675
|
+
logger.info(`Using cached YAML for: ${testName}`);
|
|
676
|
+
|
|
677
|
+
// Update usage stats
|
|
678
|
+
await updateCacheUsage(cached.hash);
|
|
679
|
+
|
|
680
|
+
// Run with cached YAML
|
|
681
|
+
return runTest(cached.yaml, testName, {
|
|
682
|
+
...options,
|
|
683
|
+
fromCache: true,
|
|
684
|
+
promptHash: cached.hash,
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// No cache, run with provided YAML
|
|
689
|
+
return runTest(yamlContent, testName, {
|
|
690
|
+
...options,
|
|
691
|
+
prompt,
|
|
692
|
+
fromCache: false,
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Save a test to cache after successful execution
|
|
698
|
+
*/
|
|
699
|
+
export async function saveTestToCache(prompt, yamlContent, testName, appId = null) {
|
|
700
|
+
try {
|
|
701
|
+
const result = await saveToCache(prompt, yamlContent, testName, appId);
|
|
702
|
+
|
|
703
|
+
return {
|
|
704
|
+
content: [
|
|
705
|
+
{
|
|
706
|
+
type: 'text',
|
|
707
|
+
text: JSON.stringify({
|
|
708
|
+
success: result.success,
|
|
709
|
+
hash: result.hash,
|
|
710
|
+
message: result.success
|
|
711
|
+
? `✅ Test cached! Future runs of this prompt will use the saved YAML automatically.`
|
|
712
|
+
: `❌ Failed to cache: ${result.error}`,
|
|
713
|
+
hint: result.success
|
|
714
|
+
? 'When you run the same prompt again, the cached YAML will be used for faster execution.'
|
|
715
|
+
: null,
|
|
716
|
+
}),
|
|
717
|
+
},
|
|
718
|
+
],
|
|
719
|
+
};
|
|
720
|
+
} catch (error) {
|
|
721
|
+
return {
|
|
722
|
+
content: [
|
|
723
|
+
{
|
|
724
|
+
type: 'text',
|
|
725
|
+
text: JSON.stringify({
|
|
726
|
+
success: false,
|
|
727
|
+
error: error.message,
|
|
728
|
+
}),
|
|
729
|
+
},
|
|
730
|
+
],
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* List all cached tests
|
|
737
|
+
*/
|
|
738
|
+
export async function listCachedTests() {
|
|
739
|
+
try {
|
|
740
|
+
const result = await listYamlCache();
|
|
741
|
+
|
|
742
|
+
return {
|
|
743
|
+
content: [
|
|
744
|
+
{
|
|
745
|
+
type: 'text',
|
|
746
|
+
text: JSON.stringify({
|
|
747
|
+
success: result.success,
|
|
748
|
+
count: result.count,
|
|
749
|
+
cacheDir: result.cacheDir,
|
|
750
|
+
tests: result.entries?.map(e => ({
|
|
751
|
+
hash: e.hash,
|
|
752
|
+
testName: e.testName,
|
|
753
|
+
appId: e.appId,
|
|
754
|
+
prompt: e.prompt,
|
|
755
|
+
cachedAt: e.cachedAt,
|
|
756
|
+
lastUsed: e.lastUsed,
|
|
757
|
+
executionCount: e.executionCount,
|
|
758
|
+
})) || [],
|
|
759
|
+
message: result.count > 0
|
|
760
|
+
? `Found ${result.count} cached test(s). These will be reused automatically when the same prompt is run.`
|
|
761
|
+
: 'No cached tests. Run successful tests and save them for faster future execution.',
|
|
762
|
+
}),
|
|
763
|
+
},
|
|
764
|
+
],
|
|
765
|
+
};
|
|
766
|
+
} catch (error) {
|
|
767
|
+
return {
|
|
768
|
+
content: [
|
|
769
|
+
{
|
|
770
|
+
type: 'text',
|
|
771
|
+
text: JSON.stringify({
|
|
772
|
+
success: false,
|
|
773
|
+
error: error.message,
|
|
774
|
+
}),
|
|
775
|
+
},
|
|
776
|
+
],
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* Clear all cached tests
|
|
783
|
+
*/
|
|
784
|
+
export async function clearTestCache() {
|
|
785
|
+
try {
|
|
786
|
+
const result = await clearYamlCache();
|
|
787
|
+
|
|
788
|
+
return {
|
|
789
|
+
content: [
|
|
790
|
+
{
|
|
791
|
+
type: 'text',
|
|
792
|
+
text: JSON.stringify({
|
|
793
|
+
success: result.success,
|
|
794
|
+
cleared: result.cleared,
|
|
795
|
+
message: result.success
|
|
796
|
+
? `✅ Cleared ${result.cleared} cached test(s). All tests will generate fresh YAML on next run.`
|
|
797
|
+
: `❌ Failed to clear cache: ${result.error}`,
|
|
798
|
+
}),
|
|
799
|
+
},
|
|
800
|
+
],
|
|
801
|
+
};
|
|
802
|
+
} catch (error) {
|
|
803
|
+
return {
|
|
804
|
+
content: [
|
|
805
|
+
{
|
|
806
|
+
type: 'text',
|
|
807
|
+
text: JSON.stringify({
|
|
808
|
+
success: false,
|
|
809
|
+
error: error.message,
|
|
810
|
+
}),
|
|
811
|
+
},
|
|
812
|
+
],
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* Delete a specific cached test
|
|
819
|
+
*/
|
|
820
|
+
export async function deleteCachedTest(hash) {
|
|
821
|
+
try {
|
|
822
|
+
const result = await deleteFromCache(hash);
|
|
823
|
+
|
|
824
|
+
return {
|
|
825
|
+
content: [
|
|
826
|
+
{
|
|
827
|
+
type: 'text',
|
|
828
|
+
text: JSON.stringify({
|
|
829
|
+
success: result.success,
|
|
830
|
+
message: result.success
|
|
831
|
+
? `✅ Cached test deleted. A new YAML will be generated on next run.`
|
|
832
|
+
: `❌ ${result.error}`,
|
|
833
|
+
}),
|
|
834
|
+
},
|
|
835
|
+
],
|
|
836
|
+
};
|
|
837
|
+
} catch (error) {
|
|
838
|
+
return {
|
|
839
|
+
content: [
|
|
840
|
+
{
|
|
841
|
+
type: 'text',
|
|
842
|
+
text: JSON.stringify({
|
|
843
|
+
success: false,
|
|
844
|
+
error: error.message,
|
|
845
|
+
}),
|
|
846
|
+
},
|
|
847
|
+
],
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* Get cache statistics
|
|
854
|
+
*/
|
|
855
|
+
export async function getTestCacheStats() {
|
|
856
|
+
try {
|
|
857
|
+
const result = await getCacheStats();
|
|
858
|
+
|
|
859
|
+
return {
|
|
860
|
+
content: [
|
|
861
|
+
{
|
|
862
|
+
type: 'text',
|
|
863
|
+
text: JSON.stringify({
|
|
864
|
+
success: result.success,
|
|
865
|
+
stats: result.stats,
|
|
866
|
+
message: result.stats?.totalCached > 0
|
|
867
|
+
? `Cache has ${result.stats.totalCached} test(s) with ${result.stats.totalExecutions} total executions.`
|
|
868
|
+
: 'Cache is empty.',
|
|
869
|
+
}),
|
|
870
|
+
},
|
|
871
|
+
],
|
|
872
|
+
};
|
|
873
|
+
} catch (error) {
|
|
874
|
+
return {
|
|
875
|
+
content: [
|
|
876
|
+
{
|
|
877
|
+
type: 'text',
|
|
878
|
+
text: JSON.stringify({
|
|
879
|
+
success: false,
|
|
880
|
+
error: error.message,
|
|
881
|
+
}),
|
|
882
|
+
},
|
|
883
|
+
],
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* Lookup cached YAML for a prompt
|
|
890
|
+
*/
|
|
891
|
+
export async function lookupCachedTest(prompt, appId = null) {
|
|
892
|
+
try {
|
|
893
|
+
const cached = await lookupCache(prompt, appId);
|
|
894
|
+
|
|
895
|
+
if (cached) {
|
|
896
|
+
return {
|
|
897
|
+
content: [
|
|
898
|
+
{
|
|
899
|
+
type: 'text',
|
|
900
|
+
text: JSON.stringify({
|
|
901
|
+
success: true,
|
|
902
|
+
found: true,
|
|
903
|
+
hash: cached.hash,
|
|
904
|
+
testName: cached.testName,
|
|
905
|
+
appId: cached.appId,
|
|
906
|
+
cachedAt: cached.cachedAt,
|
|
907
|
+
lastUsed: cached.lastUsed,
|
|
908
|
+
executionCount: cached.executionCount,
|
|
909
|
+
yaml: cached.yaml,
|
|
910
|
+
message: '⚡ Found cached YAML for this prompt. Will be used for faster execution.',
|
|
911
|
+
}),
|
|
912
|
+
},
|
|
913
|
+
],
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
return {
|
|
918
|
+
content: [
|
|
919
|
+
{
|
|
920
|
+
type: 'text',
|
|
921
|
+
text: JSON.stringify({
|
|
922
|
+
success: true,
|
|
923
|
+
found: false,
|
|
924
|
+
message: 'No cached YAML found for this prompt. A new YAML will be generated.',
|
|
554
925
|
}),
|
|
555
926
|
},
|
|
556
927
|
],
|
|
557
928
|
};
|
|
558
929
|
} catch (error) {
|
|
559
|
-
logger.error('Test suite with report error', { error: error.message });
|
|
560
930
|
return {
|
|
561
931
|
content: [
|
|
562
932
|
{
|
|
@@ -577,4 +947,12 @@ export default {
|
|
|
577
947
|
generateTestReport,
|
|
578
948
|
listTestReports,
|
|
579
949
|
runTestSuiteWithReport,
|
|
950
|
+
// Cache management
|
|
951
|
+
runTestWithCache,
|
|
952
|
+
saveTestToCache,
|
|
953
|
+
listCachedTests,
|
|
954
|
+
clearTestCache,
|
|
955
|
+
deleteCachedTest,
|
|
956
|
+
getTestCacheStats,
|
|
957
|
+
lookupCachedTest,
|
|
580
958
|
};
|