config-manager.ts•8.82 kB
/**
* Configuration Manager
* Handles loading configuration from files and environment variables
*/
import { readFileSync, writeFileSync, existsSync } from 'fs';
import { join } from 'path';
import {
ServerConfig,
EnvironmentConfig,
ConfigValidationResult
} from '../types/config.js';
export class ConfigManager {
private config: ServerConfig | null = null;
private configPath: string;
constructor(configPath?: string) {
this.configPath = configPath || join(process.cwd(), 'config.json');
}
/**
* Load configuration from file and environment variables
*/
public async loadConfig(): Promise<ServerConfig> {
let fileConfig: Partial<ServerConfig> = {};
// Try to load from config file
if (existsSync(this.configPath)) {
try {
const configContent = readFileSync(this.configPath, 'utf-8');
fileConfig = JSON.parse(configContent);
} catch (error) {
throw new Error(`Failed to parse config file: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
} else {
// Generate default config if file doesn't exist
await this.generateDefaultConfig();
fileConfig = this.getDefaultConfig();
}
// Merge with environment variables
const envConfig = this.loadEnvironmentConfig();
this.config = this.mergeConfigs(fileConfig, envConfig);
// Validate configuration
const validation = this.validateConfig(this.config);
if (!validation.isValid) {
throw new Error(`Configuration validation failed: ${validation.errors.join(', ')}`);
}
return this.config;
}
/**
* Get current configuration
*/
public getConfig(): ServerConfig {
if (!this.config) {
throw new Error('Configuration not loaded. Call loadConfig() first.');
}
return this.config;
}
/**
* Generate default configuration file
*/
public async generateDefaultConfig(): Promise<void> {
const defaultConfig = this.getDefaultConfig();
try {
writeFileSync(this.configPath, JSON.stringify(defaultConfig, null, 2));
console.log(`Default configuration generated at: ${this.configPath}`);
} catch (error) {
throw new Error(`Failed to generate default config: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Load configuration from environment variables
*/
private loadEnvironmentConfig(): Partial<ServerConfig> {
const env = process.env as EnvironmentConfig;
const envConfig: Partial<ServerConfig> = {};
// Google configuration
if (env.GOOGLE_CLIENT_ID || env.GOOGLE_CLIENT_SECRET || env.GOOGLE_REDIRECT_URI) {
envConfig.google = {
clientId: env.GOOGLE_CLIENT_ID || '',
clientSecret: env.GOOGLE_CLIENT_SECRET || '',
redirectUri: env.GOOGLE_REDIRECT_URI || 'http://localhost:8080/callback',
scopes: ['https://www.googleapis.com/auth/drive.readonly'],
applicationName: 'Google Drive MCP Server'
};
}
// Cache configuration
if (env.CACHE_DIR || env.CACHE_MAX_SIZE || env.CACHE_TTL) {
envConfig.cache = {
directory: env.CACHE_DIR || './cache',
maxSize: env.CACHE_MAX_SIZE || '1GB',
defaultTTL: env.CACHE_TTL ? parseInt(env.CACHE_TTL) : 3600,
cleanupInterval: 300,
enableCompression: true
};
}
// Processing configuration
if (env.MAX_FILE_SIZE) {
envConfig.processing = {
maxFileSize: env.MAX_FILE_SIZE,
supportedMimeTypes: [
'application/vnd.google-apps.document',
'application/pdf',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'text/plain',
'text/markdown'
],
chunkSize: 4000,
summaryLength: 500
};
}
// Server configuration
if (env.LOG_LEVEL || env.SERVER_PORT) {
envConfig.server = {
port: env.SERVER_PORT ? parseInt(env.SERVER_PORT) : 3000,
logLevel: (env.LOG_LEVEL as any) || 'info',
requestTimeout: 30000,
maxConcurrentRequests: 10,
enableMetrics: true
};
}
return envConfig;
}
/**
* Get default configuration
*/
private getDefaultConfig(): ServerConfig {
return {
google: {
clientId: process.env.GOOGLE_CLIENT_ID || '',
clientSecret: process.env.GOOGLE_CLIENT_SECRET || '',
redirectUri: process.env.GOOGLE_REDIRECT_URI || 'http://localhost:8080/callback',
scopes: ['https://www.googleapis.com/auth/drive.readonly'],
applicationName: 'Google Drive MCP Server'
},
cache: {
directory: './cache',
maxSize: '1GB',
defaultTTL: 3600,
cleanupInterval: 300,
enableCompression: true
},
processing: {
maxFileSize: '100MB',
supportedMimeTypes: [
'application/vnd.google-apps.document',
'application/pdf',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'text/plain',
'text/markdown',
'text/html'
],
chunkSize: 4000,
summaryLength: 500,
enableOCR: false,
ocrLanguages: ['en']
},
server: {
port: 3000,
logLevel: 'info',
requestTimeout: 30000,
maxConcurrentRequests: 10,
enableMetrics: true
}
};
}
/**
* Merge file config with environment config
*/
private mergeConfigs(fileConfig: Partial<ServerConfig>, envConfig: Partial<ServerConfig>): ServerConfig {
const defaultConfig = this.getDefaultConfig();
return {
google: {
...defaultConfig.google,
...fileConfig.google,
...envConfig.google
},
cache: {
...defaultConfig.cache,
...fileConfig.cache,
...envConfig.cache
},
processing: {
...defaultConfig.processing,
...fileConfig.processing,
...envConfig.processing
},
server: {
...defaultConfig.server,
...fileConfig.server,
...envConfig.server
}
};
}
/**
* Validate configuration
*/
private validateConfig(config: ServerConfig): ConfigValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
// Validate Google configuration
if (!config.google.clientId) {
errors.push('Google Client ID is required');
}
if (!config.google.clientSecret) {
errors.push('Google Client Secret is required');
}
if (!config.google.redirectUri) {
errors.push('Google Redirect URI is required');
}
// Validate server configuration
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');
}
// Validate processing configuration
if (config.processing.chunkSize < 100) {
warnings.push('Chunk size is very small, may impact performance');
}
if (config.processing.summaryLength < 50) {
warnings.push('Summary length is very short');
}
// Validate cache configuration
if (config.cache.defaultTTL < 60) {
warnings.push('Cache TTL is very short, may impact performance');
}
return {
isValid: errors.length === 0,
errors,
warnings
};
}
/**
* Update configuration and save to file
*/
public async updateConfig(updates: Partial<ServerConfig>): Promise<void> {
if (!this.config) {
throw new Error('Configuration not loaded. Call loadConfig() first.');
}
// Merge updates with current config
this.config = this.mergeConfigs(this.config, updates);
// Validate updated configuration
const validation = this.validateConfig(this.config);
if (!validation.isValid) {
throw new Error(`Configuration validation failed: ${validation.errors.join(', ')}`);
}
// Save to file
try {
writeFileSync(this.configPath, JSON.stringify(this.config, null, 2));
} catch (error) {
throw new Error(`Failed to save config: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Get configuration value by path
*/
public getConfigValue<T>(path: string): T | undefined {
if (!this.config) {
return undefined;
}
const keys = path.split('.');
let current: any = this.config;
for (const key of keys) {
if (current && typeof current === 'object' && key in current) {
current = current[key];
} else {
return undefined;
}
}
return current as T;
}
}
// Export singleton instance
export const configManager = new ConfigManager();