analyze_code
Analyze code files to extract statistics like line counts and function totals, supporting language-specific filtering for targeted insights.
Instructions
Analyzes code files and provides statistics.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | The target file or directory path. | |
| language | No | Optional language filter. | |
| countLines | No | Whether to count lines of code. | |
| countFunctions | No | Whether to count functions. |
Implementation Reference
- src/tools/analyze_code.js:15-147 (handler)The primary handler function `handleRequest` that implements the core logic of the `analyze_code` tool: resolves paths, lists files recursively if directory, filters by language, reads contents, counts lines and functions per language heuristics, and returns aggregated statistics.async function handleRequest(parameters) { console.error('analyze_code: Starting execution'); const startTime = Date.now(); const { path: targetPath, language, countLines = true, countFunctions = true } = parameters; if (!targetPath) { throw new Error("Missing required parameter: 'path'."); } // Resolve to absolute path const absolutePath = path.resolve(targetPath); console.error(`analyze_code: Resolved path to ${absolutePath}`); let rootPath = absolutePath; // Assume path is directory initially let filesToProcess = []; // Validate path existence and determine if it's a file or directory try { const stats = await fs.stat(absolutePath); console.error(`analyze_code: Path exists, checking type`); if (stats.isDirectory()) { // Path is a directory, list files within it console.error(`analyze_code: Path is a directory, listing files`); rootPath = absolutePath; // Keep rootPath as the directory itself // Get all files in the directory recursively filesToProcess = await listFilesRecursive(absolutePath); // Filter by language if specified if (language) { const extensions = getExtensionsForLanguage(language); filesToProcess = filesToProcess.filter(file => { const ext = path.extname(file).toLowerCase(); return extensions.includes(ext); }); } console.error(`analyze_code: Found ${filesToProcess.length} files to analyze`); } else if (stats.isFile()) { // Path is a single file console.error(`analyze_code: Path is a file`); rootPath = path.dirname(absolutePath); // Set rootPath to the parent directory const relativeFilePath = path.basename(absolutePath); // Check if language filter applies if (language) { const extensions = getExtensionsForLanguage(language); const ext = path.extname(relativeFilePath).toLowerCase(); if (!extensions.includes(ext)) { console.error(`analyze_code: File ${relativeFilePath} does not match language filter ${language}`); filesToProcess = []; // Skip if language doesn't match } else { filesToProcess.push(relativeFilePath); } } else { filesToProcess.push(relativeFilePath); } console.error(`analyze_code: Added single file to process: ${relativeFilePath}`); } else { // Path exists but is not a file or directory (e.g., socket, fifo) throw new Error(`Path '${targetPath}' is not a file or directory.`); } } catch (error) { if (error.code === 'ENOENT') { throw new Error(`Path '${targetPath}' not found.`); } throw new Error(`Error accessing path '${targetPath}': ${error.message}`); } if (filesToProcess.length === 0) { // If no files are left after filtering console.error(`analyze_code: No files to process, returning empty analysis`); return { analysis: { totalFiles: 0, totalLines: 0, totalFunctions: 0, fileBreakdown: [] } }; } // Read the content of the files console.error(`analyze_code: Reading content of ${filesToProcess.length} files`); const fileContentsMap = await readFiles(filesToProcess, rootPath); // Analyze the files console.error(`analyze_code: Analyzing files`); const analysis = { totalFiles: filesToProcess.length, totalLines: 0, totalFunctions: 0, fileBreakdown: [] }; // Sort file paths before analyzing for deterministic output const sortedRelativePaths = [...fileContentsMap.keys()].sort((a, b) => a.localeCompare(b)); for (const relativeFilePath of sortedRelativePaths) { const content = fileContentsMap.get(relativeFilePath); if (content !== undefined) { // Check if file read was successful const fileAnalysis = { file: relativeFilePath, lines: 0, functions: 0 }; // Count lines if requested if (countLines) { fileAnalysis.lines = content.split('\n').length; analysis.totalLines += fileAnalysis.lines; } // Count functions if requested if (countFunctions) { fileAnalysis.functions = countFunctionsInCode(content, path.extname(relativeFilePath)); analysis.totalFunctions += fileAnalysis.functions; } analysis.fileBreakdown.push(fileAnalysis); } } const executionTime = Date.now() - startTime; console.error(`analyze_code: Execution completed in ${executionTime}ms`); return { analysis }; }
- src/mcp-server.js:186-211 (registration)Registration of the `analyze_code` tool using `server.tool()`, including the tool name, description, Zod input schema, and wrapper that calls the handler and adapts the result.// Register the analyze_code tool if (analyzeCodeHandler) { server.tool( 'analyze_code', 'Analyzes code files and provides statistics.', { path: z.string().describe('The target file or directory path.'), language: z.string().optional().describe('Optional language filter.'), countLines: z.boolean().optional().describe('Whether to count lines of code.'), countFunctions: z.boolean().optional().describe('Whether to count functions.') }, async (params) => { logInfo(`Executing analyze_code tool with params: ${JSON.stringify(params)}`); try { const startTime = Date.now(); const result = await analyzeCodeHandler(params); const executionTime = Date.now() - startTime; logDebug(`analyze_code completed in ${executionTime}ms`); return adaptToolResult(result); } catch (error) { logError('Error in analyze_code tool:', error); throw error; } } ); }
- src/mcp-server.js:191-196 (schema)Zod schema defining the input parameters for the `analyze_code` tool: path (required), language (optional), countLines and countFunctions (optional booleans).{ path: z.string().describe('The target file or directory path.'), language: z.string().optional().describe('Optional language filter.'), countLines: z.boolean().optional().describe('Whether to count lines of code.'), countFunctions: z.boolean().optional().describe('Whether to count functions.') },
- src/tools/analyze_code.js:204-236 (helper)`countFunctionsInCode` helper: language-specific regex-based function counting (JS/TS arrow funcs/methods, Python def, Java methods).function countFunctionsInCode(code, fileExtension) { // This is a very simple implementation and won't catch all functions // A real implementation would use language-specific parsers let count = 0; // JavaScript/TypeScript if (['.js', '.jsx', '.ts', '.tsx', '.mjs'].includes(fileExtension.toLowerCase())) { // Count function declarations const functionMatches = code.match(/function\s+\w+\s*\(/g) || []; count += functionMatches.length; // Count arrow functions const arrowMatches = code.match(/\w+\s*=\s*\([^)]*\)\s*=>/g) || []; count += arrowMatches.length; // Count method definitions const methodMatches = code.match(/\w+\s*\([^)]*\)\s*{/g) || []; count += methodMatches.length; } // Python else if (['.py', '.pyw'].includes(fileExtension.toLowerCase())) { const functionMatches = code.match(/def\s+\w+\s*\(/g) || []; count += functionMatches.length; } // Java else if (['.java'].includes(fileExtension.toLowerCase())) { // This is a very simplified approach const methodMatches = code.match(/\w+\s+\w+\s*\([^)]*\)\s*{/g) || []; count += methodMatches.length; } return count; }
- src/tools/analyze_code.js:154-169 (helper)`listFilesRecursive` helper: recursively lists all files in a directory, returning relative paths.async function listFilesRecursive(dir, baseDir = dir, result = []) { const entries = await fs.readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); const relativePath = path.relative(baseDir, fullPath); if (entry.isDirectory()) { await listFilesRecursive(fullPath, baseDir, result); } else if (entry.isFile()) { result.push(relativePath); } } return result; }