config-validator.tsā¢11.7 kB
/**
* Configuration Validator
* Validates configuration and diagnoses common issues
*/
import chalk from 'chalk';
import ora from 'ora';
import { existsSync, accessSync, constants } from 'fs';
import { ConfigManager } from '../config/config-manager.js';
import { AuthService } from '../auth/auth-service.js';
import { ServerConfig } from '../types/config.js';
export interface ValidationResult {
isValid: boolean;
errors: string[];
warnings: string[];
}
export class ConfigValidator {
private configManager: ConfigManager;
constructor(configPath?: string) {
this.configManager = new ConfigManager(configPath);
}
/**
* Validate configuration
*/
async validate(): Promise<ValidationResult> {
const errors: string[] = [];
const warnings: string[] = [];
try {
const config = await this.configManager.loadConfig();
// Validate Google OAuth configuration
this.validateGoogleConfig(config, errors, warnings);
// Validate cache configuration
this.validateCacheConfig(config, errors, warnings);
// Validate processing configuration
this.validateProcessingConfig(config, errors, warnings);
// Validate server configuration
this.validateServerConfig(config, errors, warnings);
return {
isValid: errors.length === 0,
errors,
warnings
};
} catch (error) {
errors.push(`Failed to load configuration: ${error instanceof Error ? error.message : 'Unknown error'}`);
return {
isValid: false,
errors,
warnings
};
}
}
/**
* Comprehensive diagnosis of common issues
*/
async diagnose(): Promise<void> {
console.log(chalk.blue.bold('\nš Google Drive MCP Server - System Diagnosis\n'));
// Check 1: Configuration file
await this.checkConfigurationFile();
// Check 2: Google OAuth setup
await this.checkGoogleOAuthSetup();
// Check 3: Authentication status
await this.checkAuthenticationStatus();
// Check 4: File system permissions
await this.checkFileSystemPermissions();
// Check 5: Network connectivity
await this.checkNetworkConnectivity();
// Check 6: Dependencies
await this.checkDependencies();
console.log(chalk.blue('\nš Diagnosis complete!'));
}
/**
* Validate Google OAuth configuration
*/
private validateGoogleConfig(config: ServerConfig, errors: string[], warnings: string[]): void {
if (!config.google.clientId) {
errors.push('Google Client ID is required');
} else if (!config.google.clientId.includes('.googleusercontent.com')) {
warnings.push('Google Client ID should end with .googleusercontent.com');
}
if (!config.google.clientSecret) {
errors.push('Google Client Secret is required');
}
if (!config.google.redirectUri) {
errors.push('Google Redirect URI is required');
} else {
try {
new URL(config.google.redirectUri);
} catch {
errors.push('Google Redirect URI must be a valid URL');
}
}
if (!config.google.scopes || config.google.scopes.length === 0) {
errors.push('Google OAuth scopes are required');
} else if (!config.google.scopes.includes('https://www.googleapis.com/auth/drive.readonly')) {
warnings.push('Drive readonly scope is recommended for basic functionality');
}
}
/**
* Validate cache configuration
*/
private validateCacheConfig(config: ServerConfig, errors: string[], warnings: string[]): void {
if (!config.cache.directory) {
errors.push('Cache directory is required');
}
if (config.cache.defaultTTL < 60) {
warnings.push('Cache TTL is very short, may impact performance');
}
if (config.cache.defaultTTL > 86400) {
warnings.push('Cache TTL is very long, files may become stale');
}
// Parse max size
const maxSize = config.cache.maxSize;
if (maxSize) {
const sizeMatch = maxSize.match(/^(\d+(?:\.\d+)?)\s*(GB|MB|KB|B)?$/i);
if (!sizeMatch) {
errors.push('Cache max size format is invalid (e.g., "1GB", "500MB")');
}
}
}
/**
* Validate processing configuration
*/
private validateProcessingConfig(config: ServerConfig, errors: string[], warnings: string[]): void {
if (config.processing.chunkSize < 100) {
warnings.push('Chunk size is very small, may impact performance');
}
if (config.processing.chunkSize > 10000) {
warnings.push('Chunk size is very large, may cause memory issues');
}
if (config.processing.summaryLength < 50) {
warnings.push('Summary length is very short');
}
// Parse max file size
const maxFileSize = config.processing.maxFileSize;
if (maxFileSize) {
const sizeMatch = maxFileSize.match(/^(\d+(?:\.\d+)?)\s*(GB|MB|KB|B)?$/i);
if (!sizeMatch) {
errors.push('Processing max file size format is invalid (e.g., "100MB", "1GB")');
}
}
}
/**
* Validate server configuration
*/
private validateServerConfig(config: ServerConfig, errors: string[], warnings: string[]): void {
if (config.server.port < 1 || config.server.port > 65535) {
errors.push('Server port must be between 1 and 65535');
}
if (!['debug', 'info', 'warn', 'error'].includes(config.server.logLevel)) {
errors.push('Log level must be one of: debug, info, warn, error');
}
if (config.server.requestTimeout < 1000) {
warnings.push('Request timeout is very short, may cause timeouts');
}
if (config.server.maxConcurrentRequests && config.server.maxConcurrentRequests < 1) {
errors.push('Max concurrent requests must be at least 1');
}
}
/**
* Check configuration file
*/
private async checkConfigurationFile(): Promise<void> {
const spinner = ora('Checking configuration file...').start();
try {
await this.configManager.loadConfig();
spinner.succeed('Configuration file loaded successfully');
// Validate configuration
const validation = await this.validate();
if (!validation.isValid) {
console.log(chalk.red(' ā Configuration validation failed:'));
validation.errors.forEach(error => console.log(chalk.red(` ⢠${error}`)));
}
if (validation.warnings.length > 0) {
console.log(chalk.yellow(' ā Configuration warnings:'));
validation.warnings.forEach(warning => console.log(chalk.yellow(` ⢠${warning}`)));
}
} catch (error) {
spinner.fail('Configuration file check failed');
console.log(chalk.red(` Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
}
}
/**
* Check Google OAuth setup
*/
private async checkGoogleOAuthSetup(): Promise<void> {
const spinner = ora('Checking Google OAuth setup...').start();
try {
const config = await this.configManager.loadConfig();
if (!config.google.clientId || !config.google.clientSecret) {
spinner.fail('Google OAuth credentials not configured');
console.log(chalk.yellow(' Run "google-drive-mcp auth setup" to configure OAuth'));
return;
}
spinner.succeed('Google OAuth credentials configured');
} catch (error) {
spinner.fail('Failed to check Google OAuth setup');
console.log(chalk.red(` Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
}
}
/**
* Check authentication status
*/
private async checkAuthenticationStatus(): Promise<void> {
const spinner = ora('Checking authentication status...').start();
try {
const authService = new AuthService();
await authService.initialize();
const status = await authService.validateAuthentication();
if (status.isValid) {
spinner.succeed('Authentication is valid');
} else {
spinner.fail('Authentication failed');
console.log(chalk.red(` Error: ${status.error}`));
if (status.needsSetup) {
console.log(chalk.yellow(' Run "google-drive-mcp auth setup" to configure authentication'));
} else if (status.needsReauth) {
console.log(chalk.yellow(' Run "google-drive-mcp auth refresh" to refresh tokens'));
}
}
} catch (error) {
spinner.fail('Failed to check authentication');
console.log(chalk.red(` Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
}
}
/**
* Check file system permissions
*/
private async checkFileSystemPermissions(): Promise<void> {
const spinner = ora('Checking file system permissions...').start();
try {
const config = await this.configManager.loadConfig();
const cacheDir = config.cache.directory;
// Check if cache directory exists and is writable
if (existsSync(cacheDir)) {
try {
accessSync(cacheDir, constants.R_OK | constants.W_OK);
spinner.succeed('File system permissions OK');
} catch {
spinner.fail('Cache directory is not writable');
console.log(chalk.red(` Directory: ${cacheDir}`));
}
} else {
spinner.warn('Cache directory does not exist (will be created on first use)');
console.log(chalk.yellow(` Directory: ${cacheDir}`));
}
} catch (error) {
spinner.fail('Failed to check file system permissions');
console.log(chalk.red(` Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
}
}
/**
* Check network connectivity
*/
private async checkNetworkConnectivity(): Promise<void> {
const spinner = ora('Checking network connectivity...').start();
try {
// Test Google APIs endpoint
const response = await fetch('https://www.googleapis.com/drive/v3/', {
method: 'HEAD',
signal: AbortSignal.timeout(5000)
});
if (response.ok || response.status === 401) {
// 401 is expected without authentication
spinner.succeed('Network connectivity OK');
} else {
spinner.fail('Network connectivity issues');
console.log(chalk.red(` HTTP Status: ${response.status}`));
}
} catch (error) {
spinner.fail('Network connectivity failed');
if (error instanceof Error) {
if (error.name === 'AbortError') {
console.log(chalk.red(' Timeout connecting to Google APIs'));
} else {
console.log(chalk.red(` Error: ${error.message}`));
}
}
}
}
/**
* Check dependencies
*/
private async checkDependencies(): Promise<void> {
const spinner = ora('Checking dependencies...').start();
try {
// Check required modules
const requiredModules = [
'@modelcontextprotocol/sdk',
'googleapis',
'pdf-parse',
'mammoth',
'node-cache'
];
const missingModules: string[] = [];
for (const module of requiredModules) {
try {
await import(module);
} catch {
missingModules.push(module);
}
}
if (missingModules.length === 0) {
spinner.succeed('All dependencies available');
} else {
spinner.fail('Missing dependencies');
console.log(chalk.red(' Missing modules:'));
missingModules.forEach(module => console.log(chalk.red(` ⢠${module}`)));
console.log(chalk.yellow(' Run "npm install" to install missing dependencies'));
}
} catch (error) {
spinner.fail('Failed to check dependencies');
console.log(chalk.red(` Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
}
}
}