cli.ts•10.1 kB
#!/usr/bin/env node
/**
* Google Drive MCP Server CLI
* Command-line interface for server management and configuration
*/
import { Command } from 'commander';
import chalk from 'chalk';
import { ConfigManager } from './config/config-manager.js';
import { AuthService } from './auth/auth-service.js';
import { AuthSetupWizard } from './cli/auth-wizard.js';
import { ConfigValidator } from './cli/config-validator.js';
import { ServerManager } from './cli/server-manager.js';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { readFileSync } from 'fs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Read package.json for version
const packagePath = join(__dirname, '../package.json');
const packageJson = JSON.parse(readFileSync(packagePath, 'utf-8'));
const program = new Command();
program
.name('google-drive-mcp')
.description('Google Drive MCP Server - CLI for managing the server')
.version(packageJson.version);
/**
* Start command - Start the MCP server
*/
program
.command('start')
.description('Start the Google Drive MCP server')
.option('-c, --config <path>', 'Path to configuration file')
.option('-p, --port <number>', 'Server port (overrides config)')
.option('-l, --log-level <level>', 'Log level (debug, info, warn, error)')
.option('--stdio', 'Use stdio transport (default for MCP)', true)
.option('--http', 'Use HTTP transport instead of stdio')
.action(async (options: any) => {
try {
const serverManager = new ServerManager(options.config);
await serverManager.start({
port: options.port ? parseInt(options.port) : undefined,
logLevel: options.logLevel,
useStdio: !options.http
});
} catch (error) {
console.error(chalk.red('Failed to start server:'), error instanceof Error ? error.message : error);
process.exit(1);
}
});
/**
* Config command group
*/
const configCmd = program
.command('config')
.description('Configuration management commands');
configCmd
.command('init')
.description('Initialize configuration with default values')
.option('-f, --force', 'Overwrite existing configuration')
.option('-c, --config <path>', 'Path to configuration file')
.action(async (options: any) => {
try {
const configManager = new ConfigManager(options.config);
await configManager.generateDefaultConfig();
console.log(chalk.green('✓ Configuration initialized successfully'));
console.log(chalk.yellow('Please update the configuration with your Google OAuth credentials'));
} catch (error) {
console.error(chalk.red('Failed to initialize configuration:'), error instanceof Error ? error.message : error);
process.exit(1);
}
});
configCmd
.command('validate')
.description('Validate current configuration')
.option('-c, --config <path>', 'Path to configuration file')
.action(async (options: any) => {
try {
const validator = new ConfigValidator(options.config);
const result = await validator.validate();
if (result.isValid) {
console.log(chalk.green('✓ Configuration is valid'));
if (result.warnings.length > 0) {
console.log(chalk.yellow('\nWarnings:'));
result.warnings.forEach(warning => console.log(chalk.yellow(` • ${warning}`)));
}
} else {
console.log(chalk.red('✗ Configuration validation failed'));
console.log(chalk.red('\nErrors:'));
result.errors.forEach(error => console.log(chalk.red(` • ${error}`)));
if (result.warnings.length > 0) {
console.log(chalk.yellow('\nWarnings:'));
result.warnings.forEach(warning => console.log(chalk.yellow(` • ${warning}`)));
}
process.exit(1);
}
} catch (error) {
console.error(chalk.red('Failed to validate configuration:'), error instanceof Error ? error.message : error);
process.exit(1);
}
});
configCmd
.command('show')
.description('Show current configuration')
.option('-c, --config <path>', 'Path to configuration file')
.option('--secrets', 'Show sensitive values (use with caution)')
.action(async (options: any) => {
try {
const configManager = new ConfigManager(options.config);
const config = await configManager.loadConfig();
// Mask sensitive values unless --secrets is used
const displayConfig = options.secrets ? config : maskSensitiveValues(config);
console.log(chalk.blue('Current Configuration:'));
console.log(JSON.stringify(displayConfig, null, 2));
} catch (error) {
console.error(chalk.red('Failed to show configuration:'), error instanceof Error ? error.message : error);
process.exit(1);
}
});
/**
* Auth command group
*/
const authCmd = program
.command('auth')
.description('Authentication management commands');
authCmd
.command('setup')
.description('Interactive authentication setup wizard')
.option('-c, --config <path>', 'Path to configuration file')
.action(async (options: any) => {
try {
const wizard = new AuthSetupWizard(options.config);
await wizard.run();
} catch (error) {
console.error(chalk.red('Authentication setup failed:'), error instanceof Error ? error.message : error);
process.exit(1);
}
});
authCmd
.command('status')
.description('Check authentication status')
.option('-c, --config <path>', 'Path to configuration file')
.action(async (options: any) => {
try {
const configManager = new ConfigManager(options.config);
await configManager.loadConfig();
const authService = new AuthService();
await authService.initialize();
const status = await authService.validateAuthentication();
if (status.isValid) {
console.log(chalk.green('✓ Authentication is valid'));
console.log(chalk.blue(' Access token is active and valid'));
} else {
console.log(chalk.red('✗ 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) {
console.error(chalk.red('Failed to check authentication status:'), error instanceof Error ? error.message : error);
process.exit(1);
}
});
authCmd
.command('refresh')
.description('Refresh authentication tokens')
.option('-c, --config <path>', 'Path to configuration file')
.action(async (options: any) => {
try {
const configManager = new ConfigManager(options.config);
await configManager.loadConfig();
const authService = new AuthService();
await authService.initialize();
const success = await authService.refreshToken();
if (success) {
console.log(chalk.green('✓ Authentication tokens refreshed successfully'));
} else {
console.log(chalk.red('✗ Failed to refresh tokens'));
console.log(chalk.yellow(' You may need to re-authenticate using "google-drive-mcp auth setup"'));
}
} catch (error) {
console.error(chalk.red('Failed to refresh tokens:'), error instanceof Error ? error.message : error);
process.exit(1);
}
});
authCmd
.command('logout')
.description('Clear stored authentication tokens')
.option('-c, --config <path>', 'Path to configuration file')
.action(async (options: any) => {
try {
const configManager = new ConfigManager(options.config);
await configManager.loadConfig();
const authService = new AuthService();
await authService.initialize();
await authService.logout();
console.log(chalk.green('✓ Logged out successfully'));
} catch (error) {
console.error(chalk.red('Failed to logout:'), error instanceof Error ? error.message : error);
process.exit(1);
}
});
/**
* Test command
*/
program
.command('test')
.description('Test server functionality')
.option('-c, --config <path>', 'Path to configuration file')
.option('--auth-only', 'Test authentication only')
.option('--drive-only', 'Test Google Drive API only')
.action(async (options: any) => {
try {
const serverManager = new ServerManager(options.config);
await serverManager.test({
authOnly: options.authOnly,
driveOnly: options.driveOnly
});
} catch (error) {
console.error(chalk.red('Test failed:'), error instanceof Error ? error.message : error);
process.exit(1);
}
});
/**
* Doctor command - Diagnose common issues
*/
program
.command('doctor')
.description('Diagnose common configuration and setup issues')
.option('-c, --config <path>', 'Path to configuration file')
.action(async (options: any) => {
try {
const validator = new ConfigValidator(options.config);
await validator.diagnose();
} catch (error) {
console.error(chalk.red('Diagnosis failed:'), error instanceof Error ? error.message : error);
process.exit(1);
}
});
/**
* Utility function to mask sensitive configuration values
*/
function maskSensitiveValues(config: any): any {
const masked = JSON.parse(JSON.stringify(config));
if (masked.google) {
if (masked.google.clientSecret) {
masked.google.clientSecret = '***MASKED***';
}
}
return masked;
}
/**
* Handle uncaught errors
*/
process.on('uncaughtException', (error) => {
console.error(chalk.red('Uncaught Exception:'), error);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error(chalk.red('Unhandled Rejection at:'), promise, chalk.red('reason:'), reason);
process.exit(1);
});
// Parse command line arguments
program.parse();