@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
JavaScript
/**
* 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