mcp-maestro-mobile-ai 1.3.1 → 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.
@@ -0,0 +1,636 @@
1
+ /**
2
+ * Zod Schemas for MCP Tool Input Validation
3
+ *
4
+ * This module provides strict input validation for all MCP tools
5
+ * using Zod schemas. These schemas are used to validate inputs
6
+ * before tool execution for security and data integrity.
7
+ *
8
+ * @module toolSchemas
9
+ * @version 1.0.0
10
+ */
11
+
12
+ import { z } from "zod";
13
+
14
+ // ============================================
15
+ // COMMON VALIDATION PATTERNS
16
+ // ============================================
17
+
18
+ /**
19
+ * Pattern for valid file paths (no path traversal)
20
+ */
21
+ const safeFilePathPattern = /^[a-zA-Z0-9_\-./\\]+$/;
22
+
23
+ /**
24
+ * Pattern for valid app IDs (e.g., com.example.app)
25
+ */
26
+ const appIdPattern = /^[a-zA-Z][a-zA-Z0-9_]*(\.[a-zA-Z][a-zA-Z0-9_]*)+$/;
27
+
28
+ /**
29
+ * Pattern for valid device IDs (emulator-5554, RF8M12345XY, 192.168.1.1:5555)
30
+ */
31
+ const deviceIdPattern = /^[a-zA-Z0-9][a-zA-Z0-9_\-.:]*$/;
32
+
33
+ /**
34
+ * Pattern for valid test/flow names
35
+ */
36
+ const namePattern = /^[a-zA-Z0-9][a-zA-Z0-9_\-\s.]*$/;
37
+
38
+ /**
39
+ * Pattern for valid screen names
40
+ */
41
+ const screenNamePattern = /^[a-zA-Z][a-zA-Z0-9_]*$/;
42
+
43
+ // ============================================
44
+ // CUSTOM REFINEMENTS
45
+ // ============================================
46
+
47
+ /**
48
+ * Refinement to check for path traversal attempts
49
+ */
50
+ const noPathTraversal = (val) => !val.includes("..");
51
+
52
+ /**
53
+ * Refinement to check for shell injection characters
54
+ */
55
+ const noShellInjection = (val) => !/[;&|`$]/.test(val);
56
+
57
+ /**
58
+ * Refinement to check for command substitution
59
+ */
60
+ const noCommandSubstitution = (val) =>
61
+ !/\$\([^)]*\)/.test(val) && !/`[^`]*`/.test(val);
62
+
63
+ // ============================================
64
+ // REUSABLE SCHEMA COMPONENTS
65
+ // ============================================
66
+
67
+ /**
68
+ * Safe file path schema
69
+ */
70
+ export const safeFilePath = z
71
+ .string()
72
+ .min(1, "File path is required")
73
+ .max(500, "File path too long")
74
+ .refine(noPathTraversal, "Path traversal not allowed (..)")
75
+ .refine(noShellInjection, "Invalid characters in path");
76
+
77
+ /**
78
+ * App ID schema
79
+ */
80
+ export const appIdSchema = z
81
+ .string()
82
+ .min(3, "App ID too short")
83
+ .max(150, "App ID too long")
84
+ .regex(appIdPattern, "Invalid app ID format (expected: com.example.app)");
85
+
86
+ /**
87
+ * Optional App ID schema
88
+ */
89
+ export const optionalAppIdSchema = z
90
+ .string()
91
+ .max(150, "App ID too long")
92
+ .regex(appIdPattern, "Invalid app ID format")
93
+ .optional();
94
+
95
+ /**
96
+ * Device ID schema
97
+ */
98
+ export const deviceIdSchema = z
99
+ .string()
100
+ .min(1, "Device ID is required")
101
+ .max(100, "Device ID too long")
102
+ .regex(deviceIdPattern, "Invalid device ID format");
103
+
104
+ /**
105
+ * Test name schema
106
+ */
107
+ export const testNameSchema = z
108
+ .string()
109
+ .min(1, "Test name is required")
110
+ .max(200, "Test name too long")
111
+ .refine(noShellInjection, "Invalid characters in test name")
112
+ .refine(noPathTraversal, "Invalid test name");
113
+
114
+ /**
115
+ * YAML content schema
116
+ */
117
+ export const yamlContentSchema = z
118
+ .string()
119
+ .min(10, "YAML content too short")
120
+ .max(50000, "YAML content too long")
121
+ .refine(noCommandSubstitution, "Command substitution not allowed in YAML");
122
+
123
+ /**
124
+ * Retries schema
125
+ */
126
+ export const retriesSchema = z
127
+ .number()
128
+ .int("Retries must be an integer")
129
+ .min(0, "Retries cannot be negative")
130
+ .max(10, "Maximum 10 retries allowed")
131
+ .optional();
132
+
133
+ // ============================================
134
+ // PROMPT TOOLS SCHEMAS
135
+ // ============================================
136
+
137
+ /**
138
+ * Schema for read_prompt_file tool
139
+ */
140
+ export const readPromptFileSchema = z.object({
141
+ file: safeFilePath,
142
+ });
143
+
144
+ /**
145
+ * Schema for list_prompt_files tool
146
+ */
147
+ export const listPromptFilesSchema = z.object({
148
+ directory: z
149
+ .string()
150
+ .max(200, "Directory path too long")
151
+ .refine(noPathTraversal, "Path traversal not allowed")
152
+ .optional(),
153
+ });
154
+
155
+ // ============================================
156
+ // DEVICE MANAGEMENT SCHEMAS
157
+ // ============================================
158
+
159
+ /**
160
+ * Schema for list_devices tool (no params)
161
+ */
162
+ export const listDevicesSchema = z.object({});
163
+
164
+ /**
165
+ * Schema for select_device tool
166
+ */
167
+ export const selectDeviceSchema = z.object({
168
+ deviceId: deviceIdSchema,
169
+ });
170
+
171
+ /**
172
+ * Schema for clear_device tool (no params)
173
+ */
174
+ export const clearDeviceSchema = z.object({});
175
+
176
+ /**
177
+ * Schema for check_device tool (no params)
178
+ */
179
+ export const checkDeviceSchema = z.object({});
180
+
181
+ /**
182
+ * Schema for check_app tool
183
+ */
184
+ export const checkAppSchema = z.object({
185
+ appId: optionalAppIdSchema,
186
+ });
187
+
188
+ // ============================================
189
+ // CONFIGURATION SCHEMAS
190
+ // ============================================
191
+
192
+ /**
193
+ * Schema for get_app_config tool (no params)
194
+ */
195
+ export const getAppConfigSchema = z.object({});
196
+
197
+ // ============================================
198
+ // VALIDATION TOOL SCHEMAS
199
+ // ============================================
200
+
201
+ /**
202
+ * Schema for validate_maestro_yaml tool
203
+ */
204
+ export const validateMaestroYamlSchema = z.object({
205
+ yaml: yamlContentSchema,
206
+ });
207
+
208
+ // ============================================
209
+ // TEST EXECUTION SCHEMAS
210
+ // ============================================
211
+
212
+ /**
213
+ * Single test item schema (for arrays)
214
+ */
215
+ export const testItemSchema = z.object({
216
+ yaml: yamlContentSchema,
217
+ name: testNameSchema,
218
+ });
219
+
220
+ /**
221
+ * Schema for run_test tool
222
+ */
223
+ export const runTestSchema = z.object({
224
+ yaml: yamlContentSchema,
225
+ name: testNameSchema,
226
+ retries: retriesSchema,
227
+ });
228
+
229
+ /**
230
+ * Schema for run_test_suite tool
231
+ */
232
+ export const runTestSuiteSchema = z.object({
233
+ tests: z
234
+ .array(testItemSchema)
235
+ .min(1, "At least one test is required")
236
+ .max(100, "Maximum 100 tests allowed"),
237
+ retries: retriesSchema,
238
+ });
239
+
240
+ /**
241
+ * Schema for run_tests_with_report tool
242
+ */
243
+ export const runTestsWithReportSchema = z.object({
244
+ tests: z
245
+ .array(testItemSchema)
246
+ .min(1, "At least one test is required")
247
+ .max(100, "Maximum 100 tests allowed"),
248
+ promptFile: z.string().max(200).optional(),
249
+ appId: optionalAppIdSchema,
250
+ retries: retriesSchema,
251
+ });
252
+
253
+ // ============================================
254
+ // RESULTS & REPORTING SCHEMAS
255
+ // ============================================
256
+
257
+ /**
258
+ * Test result item schema
259
+ */
260
+ export const testResultItemSchema = z.object({
261
+ name: z.string().min(1).max(200),
262
+ success: z.boolean(),
263
+ duration: z.number().optional(),
264
+ error: z.string().max(5000).optional(),
265
+ });
266
+
267
+ /**
268
+ * Schema for generate_report tool
269
+ */
270
+ export const generateReportSchema = z.object({
271
+ results: z
272
+ .array(testResultItemSchema)
273
+ .min(1, "At least one result is required")
274
+ .max(500, "Maximum 500 results allowed"),
275
+ promptFile: z.string().max(200).optional(),
276
+ appId: optionalAppIdSchema,
277
+ });
278
+
279
+ /**
280
+ * Schema for list_reports tool (no params)
281
+ */
282
+ export const listReportsSchema = z.object({});
283
+
284
+ /**
285
+ * Schema for get_test_results tool
286
+ */
287
+ export const getTestResultsSchema = z.object({
288
+ runId: z
289
+ .string()
290
+ .max(100, "Run ID too long")
291
+ .refine(noShellInjection, "Invalid run ID")
292
+ .optional(),
293
+ });
294
+
295
+ /**
296
+ * Schema for take_screenshot tool
297
+ */
298
+ export const takeScreenshotSchema = z.object({
299
+ name: z
300
+ .string()
301
+ .min(1, "Screenshot name is required")
302
+ .max(100, "Screenshot name too long")
303
+ .refine(noShellInjection, "Invalid characters in name")
304
+ .refine(noPathTraversal, "Invalid screenshot name"),
305
+ });
306
+
307
+ /**
308
+ * Schema for cleanup_results tool
309
+ */
310
+ export const cleanupResultsSchema = z.object({
311
+ keepLast: z
312
+ .number()
313
+ .int()
314
+ .min(1, "Must keep at least 1 result")
315
+ .max(1000, "Maximum 1000 results")
316
+ .optional(),
317
+ deleteScreenshots: z.boolean().optional(),
318
+ });
319
+
320
+ // ============================================
321
+ // APP CONTEXT/TRAINING SCHEMAS
322
+ // ============================================
323
+
324
+ /**
325
+ * Schema for register_elements tool
326
+ */
327
+ export const registerElementsSchema = z.object({
328
+ appId: appIdSchema,
329
+ elements: z.record(
330
+ z.string(),
331
+ z.object({
332
+ testId: z.string().max(200).optional(),
333
+ accessibilityLabel: z.string().max(200).optional(),
334
+ text: z.string().max(500).optional(),
335
+ type: z.string().max(50).optional(),
336
+ description: z.string().max(500).optional(),
337
+ })
338
+ ),
339
+ });
340
+
341
+ /**
342
+ * Schema for register_screen tool
343
+ */
344
+ export const registerScreenSchema = z.object({
345
+ appId: appIdSchema,
346
+ screenName: z
347
+ .string()
348
+ .min(1, "Screen name is required")
349
+ .max(100, "Screen name too long")
350
+ .regex(screenNamePattern, "Invalid screen name format"),
351
+ screenData: z.object({
352
+ description: z.string().max(500).optional(),
353
+ elements: z.array(z.string().max(100)).max(100).optional(),
354
+ actions: z.array(z.string().max(100)).max(50).optional(),
355
+ }),
356
+ });
357
+
358
+ /**
359
+ * Schema for save_successful_flow tool
360
+ */
361
+ export const saveSuccessfulFlowSchema = z.object({
362
+ appId: appIdSchema,
363
+ flowName: z
364
+ .string()
365
+ .min(1, "Flow name is required")
366
+ .max(100, "Flow name too long")
367
+ .refine(noShellInjection, "Invalid characters in flow name"),
368
+ yamlContent: yamlContentSchema,
369
+ description: z.string().max(500).optional(),
370
+ });
371
+
372
+ /**
373
+ * Schema for get_saved_flows tool
374
+ */
375
+ export const getSavedFlowsSchema = z.object({
376
+ appId: appIdSchema,
377
+ });
378
+
379
+ /**
380
+ * Schema for delete_flow tool
381
+ */
382
+ export const deleteFlowSchema = z.object({
383
+ appId: appIdSchema,
384
+ flowName: z
385
+ .string()
386
+ .min(1, "Flow name is required")
387
+ .max(100, "Flow name too long"),
388
+ });
389
+
390
+ /**
391
+ * Schema for get_ai_context tool
392
+ */
393
+ export const getAiContextSchema = z.object({
394
+ appId: appIdSchema,
395
+ });
396
+
397
+ /**
398
+ * Schema for get_full_context tool
399
+ */
400
+ export const getFullContextSchema = z.object({
401
+ appId: appIdSchema,
402
+ });
403
+
404
+ /**
405
+ * Schema for clear_app_context tool
406
+ */
407
+ export const clearAppContextSchema = z.object({
408
+ appId: appIdSchema,
409
+ });
410
+
411
+ /**
412
+ * Schema for list_app_contexts tool (no params)
413
+ */
414
+ export const listAppContextsSchema = z.object({});
415
+
416
+ // ============================================
417
+ // YAML GENERATION TOOL SCHEMAS
418
+ // ============================================
419
+
420
+ /**
421
+ * Schema for get_yaml_instructions tool
422
+ */
423
+ export const getYamlInstructionsSchema = z.object({
424
+ appId: optionalAppIdSchema,
425
+ });
426
+
427
+ /**
428
+ * Schema for validate_yaml_structure tool
429
+ */
430
+ export const validateYamlStructureSchema = z.object({
431
+ yamlContent: yamlContentSchema,
432
+ });
433
+
434
+ /**
435
+ * Valid pattern names
436
+ */
437
+ const validPatternNames = [
438
+ "login",
439
+ "form",
440
+ "search",
441
+ "navigation",
442
+ "list",
443
+ "settings",
444
+ "logout",
445
+ ];
446
+
447
+ /**
448
+ * Schema for get_test_pattern tool
449
+ */
450
+ export const getTestPatternSchema = z.object({
451
+ patternName: z.enum(validPatternNames, {
452
+ errorMap: () => ({
453
+ message: `Pattern must be one of: ${validPatternNames.join(", ")}`,
454
+ }),
455
+ }),
456
+ });
457
+
458
+ /**
459
+ * Schema for get_screen_analysis_help tool (no params)
460
+ */
461
+ export const getScreenAnalysisHelpSchema = z.object({});
462
+
463
+ // ============================================
464
+ // SCHEMA REGISTRY
465
+ // ============================================
466
+
467
+ /**
468
+ * Map of tool names to their Zod schemas
469
+ */
470
+ export const toolSchemas = {
471
+ // Prompt tools
472
+ read_prompt_file: readPromptFileSchema,
473
+ list_prompt_files: listPromptFilesSchema,
474
+
475
+ // Device management
476
+ list_devices: listDevicesSchema,
477
+ select_device: selectDeviceSchema,
478
+ clear_device: clearDeviceSchema,
479
+ check_device: checkDeviceSchema,
480
+ check_app: checkAppSchema,
481
+
482
+ // Configuration
483
+ get_app_config: getAppConfigSchema,
484
+
485
+ // Validation
486
+ validate_maestro_yaml: validateMaestroYamlSchema,
487
+
488
+ // Test execution
489
+ run_test: runTestSchema,
490
+ run_test_suite: runTestSuiteSchema,
491
+ run_tests_with_report: runTestsWithReportSchema,
492
+
493
+ // Results & reporting
494
+ generate_report: generateReportSchema,
495
+ list_reports: listReportsSchema,
496
+ get_test_results: getTestResultsSchema,
497
+ take_screenshot: takeScreenshotSchema,
498
+ cleanup_results: cleanupResultsSchema,
499
+
500
+ // App context/training
501
+ register_elements: registerElementsSchema,
502
+ register_screen: registerScreenSchema,
503
+ save_successful_flow: saveSuccessfulFlowSchema,
504
+ get_saved_flows: getSavedFlowsSchema,
505
+ delete_flow: deleteFlowSchema,
506
+ get_ai_context: getAiContextSchema,
507
+ get_full_context: getFullContextSchema,
508
+ clear_app_context: clearAppContextSchema,
509
+ list_app_contexts: listAppContextsSchema,
510
+
511
+ // YAML generation
512
+ get_yaml_instructions: getYamlInstructionsSchema,
513
+ validate_yaml_structure: validateYamlStructureSchema,
514
+ get_test_pattern: getTestPatternSchema,
515
+ get_screen_analysis_help: getScreenAnalysisHelpSchema,
516
+ };
517
+
518
+ // ============================================
519
+ // VALIDATION UTILITIES
520
+ // ============================================
521
+
522
+ /**
523
+ * Validate tool input against its schema
524
+ *
525
+ * @param {string} toolName - Name of the tool
526
+ * @param {object} input - Input to validate
527
+ * @returns {object} Validation result with success, data, and errors
528
+ */
529
+ export function validateToolInput(toolName, input) {
530
+ const schema = toolSchemas[toolName];
531
+
532
+ if (!schema) {
533
+ return {
534
+ success: true,
535
+ data: input,
536
+ warning: `No schema defined for tool: ${toolName}`,
537
+ };
538
+ }
539
+
540
+ const result = schema.safeParse(input || {});
541
+
542
+ if (result.success) {
543
+ return {
544
+ success: true,
545
+ data: result.data,
546
+ };
547
+ }
548
+
549
+ // Format errors for better readability
550
+ const formattedErrors = result.error.issues.map((issue) => ({
551
+ field: issue.path.join(".") || "root",
552
+ message: issue.message,
553
+ code: issue.code,
554
+ }));
555
+
556
+ return {
557
+ success: false,
558
+ errors: formattedErrors,
559
+ message: `Validation failed: ${formattedErrors
560
+ .map((e) => e.message)
561
+ .join(", ")}`,
562
+ };
563
+ }
564
+
565
+ /**
566
+ * Get schema for a specific tool
567
+ *
568
+ * @param {string} toolName - Name of the tool
569
+ * @returns {z.ZodSchema|null} Zod schema or null if not found
570
+ */
571
+ export function getSchemaForTool(toolName) {
572
+ return toolSchemas[toolName] || null;
573
+ }
574
+
575
+ /**
576
+ * List all tools with schemas
577
+ *
578
+ * @returns {string[]} Array of tool names
579
+ */
580
+ export function listToolsWithSchemas() {
581
+ return Object.keys(toolSchemas);
582
+ }
583
+
584
+ // ============================================
585
+ // EXPORTS
586
+ // ============================================
587
+
588
+ export default {
589
+ // Schema registry
590
+ toolSchemas,
591
+
592
+ // Utilities
593
+ validateToolInput,
594
+ getSchemaForTool,
595
+ listToolsWithSchemas,
596
+
597
+ // Common schemas
598
+ safeFilePath,
599
+ appIdSchema,
600
+ deviceIdSchema,
601
+ testNameSchema,
602
+ yamlContentSchema,
603
+ retriesSchema,
604
+
605
+ // Individual tool schemas (for direct imports)
606
+ readPromptFileSchema,
607
+ listPromptFilesSchema,
608
+ listDevicesSchema,
609
+ selectDeviceSchema,
610
+ clearDeviceSchema,
611
+ checkDeviceSchema,
612
+ checkAppSchema,
613
+ getAppConfigSchema,
614
+ validateMaestroYamlSchema,
615
+ runTestSchema,
616
+ runTestSuiteSchema,
617
+ runTestsWithReportSchema,
618
+ generateReportSchema,
619
+ listReportsSchema,
620
+ getTestResultsSchema,
621
+ takeScreenshotSchema,
622
+ cleanupResultsSchema,
623
+ registerElementsSchema,
624
+ registerScreenSchema,
625
+ saveSuccessfulFlowSchema,
626
+ getSavedFlowsSchema,
627
+ deleteFlowSchema,
628
+ getAiContextSchema,
629
+ getFullContextSchema,
630
+ clearAppContextSchema,
631
+ listAppContextsSchema,
632
+ getYamlInstructionsSchema,
633
+ validateYamlStructureSchema,
634
+ getTestPatternSchema,
635
+ getScreenAnalysisHelpSchema,
636
+ };