Skip to main content
Glama

CodeAnalysis MCP Server

by 0xjcf
watch.ts5.68 kB
import { Command } from 'commander'; import chalk from 'chalk'; import ora from 'ora'; import figures from 'figures'; import fs from 'fs'; import path from 'path'; import { getClient, callTool, closeClient } from '../utils/mcp-client.js'; import { formatOutput } from '../utils/formatters.js'; import { detectCurrentProject } from '../utils/project-detector.js'; import { getConfigValue } from '../utils/config.js'; // Use Node.js built-in fs.watch instead of requiring an additional dependency let watchTimeout: NodeJS.Timeout | null = null; const watchingFiles = new Set<string>(); export function registerWatchCommands(program: Command) { const watchCommand = program .command('watch') .description('Watch for file changes and analyze in real-time'); // Watch current project watchCommand .command('current') .description('Watch the current project for changes') .option('-i, --ignore <patterns>', 'Comma-separated patterns to ignore', 'node_modules,dist,build') .option('-d, --delay <ms>', 'Debounce delay in milliseconds', '1000') .option('-e, --extensions <ext>', 'File extensions to watch (comma-separated)', 'js,ts,jsx,tsx') .action(async (options: any, command: any) => { const { serverPath, debug, output } = command.parent.parent.opts(); try { // Detect current project const projectRoot = await detectCurrentProject(); console.log(chalk.blue(`Watching ${projectRoot} for changes...`)); console.log(chalk.gray('Press Ctrl+C to stop')); // Parse extensions to watch const extensions = options.extensions.split(',').map((ext: string) => ext.startsWith('.') ? ext : `.${ext}`); // Parse ignore patterns const ignorePatterns = options.ignore.split(','); // Connect to server and keep connection open const client = await getClient(serverPath, debug); // Set up recursive directory watching watchDirectory(projectRoot, extensions, ignorePatterns, async (filePath) => { // Debounce file changes if (watchTimeout) { clearTimeout(watchTimeout); } // Skip if we're already analyzing this file if (watchingFiles.has(filePath)) { return; } watchingFiles.add(filePath); watchTimeout = setTimeout(async () => { const spinner = ora(`Analyzing ${path.relative(projectRoot, filePath)}...`).start(); try { // Read file content const fileContent = fs.readFileSync(filePath, 'utf8'); // Call analyze-file tool const result = await callTool('calculate-metrics', { fileContent, language: path.extname(filePath).slice(1), metrics: getConfigValue('analysis.metrics', 'complexity,linesOfCode,maintainability') }, debug); spinner.succeed(`Analysis of ${path.relative(projectRoot, filePath)} complete`); // Format and display results console.log(formatOutput(result, output)); } catch (error) { spinner.fail(`Analysis failed: ${(error as Error).message}`); } finally { watchingFiles.delete(filePath); } }, parseInt(options.delay)); }); // Keep process running process.stdin.resume(); // Handle process termination process.on('SIGINT', async () => { console.log(chalk.blue('\nStopping watch mode...')); await closeClient(); process.exit(0); }); } catch (error) { console.error(chalk.red(`${figures.cross} Watch mode failed: ${(error as Error).message}`)); await closeClient(); process.exit(1); } }); return watchCommand; } /** * Watch a directory recursively for file changes */ function watchDirectory( dir: string, extensions: string[], ignorePatterns: string[], callback: (filePath: string) => void ) { // Skip ignored directories const dirName = path.basename(dir); if (ignorePatterns.some(pattern => dirName === pattern || dir.includes(`/${pattern}/`))) { return; } try { // Watch current directory fs.watch(dir, (eventType, filename) => { if (!filename) return; const filePath = path.join(dir, filename); // Skip if the file doesn't exist (might have been deleted) if (!fs.existsSync(filePath)) return; // Skip if it's an ignored pattern if (ignorePatterns.some(pattern => filename === pattern || filename.includes(`/${pattern}/`))) { return; } const stats = fs.statSync(filePath); if (stats.isDirectory()) { // If a new directory is created, start watching it watchDirectory(filePath, extensions, ignorePatterns, callback); } else if (extensions.includes(path.extname(filename))) { // If it's a file with a matching extension, trigger the callback callback(filePath); } }); // Recursively watch subdirectories const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { if (entry.isDirectory() && !ignorePatterns.includes(entry.name)) { watchDirectory(path.join(dir, entry.name), extensions, ignorePatterns, callback); } } } catch (error) { console.error(chalk.yellow(`Warning: Could not watch ${dir}: ${(error as Error).message}`)); } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/0xjcf/MCP_CodeAnalysis'

If you have feedback or need assistance with the MCP directory API, please join our Discord server