UNPKG

@dorothywebb/any-browser-mcp

Version:

Any Browser MCP - Launch Chrome with your actual data in debug mode for comprehensive browser automation

384 lines 13.7 kB
/** * Configuration Validation System using Zod * * @fileoverview Provides runtime validation of config.json with detailed error messages, * warnings for potentially problematic configurations, and automatic merging with defaults. * * @example * ```typescript * import { ConfigValidator } from './ConfigValidator.js'; * * // Validate a configuration object * const result = ConfigValidator.validate(userConfig); * if (result.success) { * console.log('Configuration is valid:', result.config); * } else { * console.error('Validation errors:', result.errors); * } * * // Merge with defaults * const mergedConfig = ConfigValidator.mergeWithDefaults(partialConfig); * ``` * * @category Configuration */ import { z } from 'zod'; import { ErrorFactory } from '../types/errors.js'; // Browser configuration schema const BrowserConfigSchema = z.object({ type: z.enum(['chrome', 'edge', 'firefox'], { errorMap: () => ({ message: 'Browser type must be one of: chrome, edge, firefox' }) }), debugPort: z.number() .int() .min(1024, 'Debug port must be >= 1024') .max(65535, 'Debug port must be <= 65535') .default(9223), autoLaunch: z.boolean().default(true), allowLaunch: z.boolean().default(true), useExistingOnly: z.boolean().default(false), endpoint: z.string().url('Endpoint must be a valid URL').optional(), connectionTimeout: z.number() .int() .min(1000, 'Connection timeout must be at least 1000ms') .max(60000, 'Connection timeout must be at most 60000ms') .default(5000), retryAttempts: z.number() .int() .min(0, 'Retry attempts must be >= 0') .max(10, 'Retry attempts must be <= 10') .default(3), retryDelay: z.number() .int() .min(100, 'Retry delay must be at least 100ms') .max(10000, 'Retry delay must be at most 10000ms') .default(1000), useSeparateInstance: z.boolean().default(true), mcpProfilePath: z.string() .min(1, 'MCP profile path cannot be empty') .default('./any-browser-mcp-profile'), copyUserData: z.boolean().default(true), launchTimeout: z.number() .int() .min(5000, 'Launch timeout must be at least 5000ms') .max(120000, 'Launch timeout must be at most 120000ms') .default(10000), confirmDestructiveActions: z.boolean().default(true), sourceProfilePath: z.string().optional() }); // Server configuration schema const ServerConfigSchema = z.object({ strategy: z.enum(['direct-cdp', 'playwright', 'hybrid', 'separate-instance'], { errorMap: () => ({ message: 'Strategy must be one of: direct-cdp, playwright, hybrid, separate-instance' }) }).default('direct-cdp'), lazyInitialization: z.boolean().default(true), initializeOnStartup: z.boolean().default(false), connectOnFirstUse: z.boolean().default(true), verbose: z.boolean().default(false), maxConcurrentConnections: z.number() .int() .min(1, 'Max concurrent connections must be >= 1') .max(20, 'Max concurrent connections must be <= 20') .default(5) }); // Safety configuration schema const SafetyConfigSchema = z.object({ neverLaunchBrowser: z.boolean().default(false), requireExistingBrowser: z.boolean().default(false), allowSeparateInstance: z.boolean().default(true), validateConnection: z.boolean().default(true), timeoutConnections: z.boolean().default(true) }); // Features configuration schema const FeaturesConfigSchema = z.object({ preventAutoStart: z.boolean().default(true), useExistingTabs: z.boolean().default(true), duplicateUserData: z.boolean().default(true), respectUserBrowser: z.boolean().default(true), separateInstance: z.boolean().default(true), autoLaunchWhenNeeded: z.boolean().default(true), confirmDestructiveActions: z.boolean().default(true), errorRetry: z.boolean().default(true), gracefulFailure: z.boolean().default(true) }); // Paths configuration schema const PathsConfigSchema = z.object({ chrome: z.record(z.string(), z.string()).default({ darwin: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', linux: 'google-chrome', win32: 'chrome.exe' }) }); // Main application configuration schema export const AppConfigSchema = z.object({ browser: BrowserConfigSchema.optional().default({ type: 'chrome', debugPort: 9223, autoLaunch: true, allowLaunch: true, useExistingOnly: false, connectionTimeout: 5000, retryAttempts: 3, retryDelay: 1000, useSeparateInstance: true, mcpProfilePath: './any-browser-mcp-profile', copyUserData: true, launchTimeout: 10000, confirmDestructiveActions: true }), server: ServerConfigSchema.optional().default({ strategy: 'direct-cdp', lazyInitialization: true, initializeOnStartup: false, connectOnFirstUse: true, verbose: false, maxConcurrentConnections: 5 }), safety: SafetyConfigSchema.optional().default({ neverLaunchBrowser: false, requireExistingBrowser: false, allowSeparateInstance: true, validateConnection: true, timeoutConnections: true }), features: FeaturesConfigSchema.optional().default({ preventAutoStart: true, useExistingTabs: true, duplicateUserData: true, respectUserBrowser: true, separateInstance: true, autoLaunchWhenNeeded: true, confirmDestructiveActions: true, errorRetry: true, gracefulFailure: true }), paths: PathsConfigSchema.optional().default({ chrome: { darwin: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', linux: 'google-chrome', win32: 'chrome.exe' } }) }); /** * Configuration Validator class providing comprehensive validation capabilities. * * @description This class uses Zod schemas to validate configuration objects, * generate helpful warnings, and merge user configurations with sensible defaults. * * @example * ```typescript * // Basic validation * const result = ConfigValidator.validate(userConfig); * * // Validation with error throwing * const validConfig = ConfigValidator.validateOrThrow(userConfig); * * // Merge with defaults * const mergedConfig = ConfigValidator.mergeWithDefaults(partialConfig); * ``` */ export class ConfigValidator { /** * Validates a configuration object against the schema. * * @param config - The configuration object to validate * @returns Validation result with success status, validated config, errors, and warnings * * @example * ```typescript * const result = ConfigValidator.validate({ * browser: { type: 'chrome', debugPort: 9223 } * }); * * if (result.success) { * console.log('Valid config:', result.config); * if (result.warnings?.length) { * console.warn('Warnings:', result.warnings); * } * } else { * console.error('Validation failed:', result.errors); * } * ``` */ static validate(config) { try { const result = AppConfigSchema.safeParse(config); if (result.success) { const warnings = this.generateWarnings(result.data); return { success: true, config: result.data, warnings }; } else { const errors = this.formatZodErrors(result.error); return { success: false, errors }; } } catch (error) { return { success: false, errors: [{ path: 'root', message: `Validation failed: ${error.message}`, code: 'VALIDATION_ERROR' }] }; } } /** * Validate and throw on error */ static validateOrThrow(config) { const result = this.validate(config); if (!result.success) { const errorMessages = result.errors.map(err => `${err.path}: ${err.message}`); throw ErrorFactory.createConfigError('Configuration validation failed', { operation: 'validateConfig', details: { errorCount: result.errors.length } }, result.errors); } // Log warnings if any if (result.warnings && result.warnings.length > 0) { console.warn('⚠️ Configuration warnings:'); result.warnings.forEach(warning => { console.warn(` ${warning.path}: ${warning.message}`); if (warning.suggestion) { console.warn(` 💡 Suggestion: ${warning.suggestion}`); } }); } return result.config; } /** * Format Zod errors into our error format */ static formatZodErrors(zodError) { return zodError.errors.map(err => ({ path: err.path.join('.') || 'root', message: err.message, code: err.code, received: 'received' in err ? err.received : undefined, expected: 'expected' in err ? err.expected : undefined })); } /** * Generate warnings for potentially problematic configurations */ static generateWarnings(config) { const warnings = []; // Check for potentially problematic port configurations if (config.browser.debugPort === 9222) { warnings.push({ path: 'browser.debugPort', message: 'Using default Chrome debug port (9222) may conflict with existing Chrome instances', suggestion: 'Consider using a different port like 9223 for MCP' }); } // Check for conflicting settings if (config.browser.useExistingOnly && config.browser.autoLaunch) { warnings.push({ path: 'browser', message: 'useExistingOnly is true but autoLaunch is also true - autoLaunch will be ignored', suggestion: 'Set autoLaunch to false when using existing browsers only' }); } // Check for security concerns if (!config.browser.confirmDestructiveActions) { warnings.push({ path: 'browser.confirmDestructiveActions', message: 'Destructive actions will not require confirmation', suggestion: 'Enable confirmDestructiveActions for safer operation' }); } // Check for performance concerns if (config.server.maxConcurrentConnections > 10) { warnings.push({ path: 'server.maxConcurrentConnections', message: 'High concurrent connection limit may impact performance', suggestion: 'Consider reducing to 5-10 for optimal performance' }); } // Check for timeout configurations if (config.browser.connectionTimeout < 3000) { warnings.push({ path: 'browser.connectionTimeout', message: 'Short connection timeout may cause connection failures', suggestion: 'Consider increasing to at least 3000ms' }); } return warnings; } /** * Get default configuration */ static getDefaultConfig() { return AppConfigSchema.parse({}); } /** * Merge user config with defaults */ static mergeWithDefaults(userConfig) { const defaultConfig = this.getDefaultConfig(); // Deep merge user config with defaults const merged = this.deepMerge(defaultConfig, userConfig); return this.validateOrThrow(merged); } /** * Deep merge two objects */ static deepMerge(target, source) { const result = { ...target }; for (const key in source) { if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) { result[key] = this.deepMerge(target[key] || {}, source[key]); } else { result[key] = source[key]; } } return result; } /** * Validate specific configuration section */ static validateSection(sectionName, config, schema) { try { const result = schema.safeParse(config); if (result.success) { return { success: true, config: result.data }; } else { const errors = this.formatZodErrors(result.error).map(err => ({ ...err, path: `${sectionName}.${err.path}` })); return { success: false, errors }; } } catch (error) { return { success: false, errors: [{ path: sectionName, message: `Section validation failed: ${error.message}`, code: 'SECTION_VALIDATION_ERROR' }] }; } } } // Export schemas for external use export { BrowserConfigSchema, ServerConfigSchema, SafetyConfigSchema, FeaturesConfigSchema, PathsConfigSchema }; //# sourceMappingURL=ConfigValidator.js.map