Skip to main content
Glama
code-analysis.ts7.04 kB
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; import fs from 'fs-extra'; import path from 'path'; interface CodeAnalysis { fileCount: number; totalLines: number; totalCharacters: number; languageBreakdown: { [key: string]: number }; complexityScore: number; issues: string[]; } export default function registerCodeAnalysisTool(server: McpServer) { server.registerTool( 'code-analysis', { title: 'Code Analysis', description: 'Analyzes code files for complexity, structure, and potential issues.', inputSchema: { directory: z.string().describe('Directory path to analyze'), include_patterns: z.array(z.string()).optional().default(['**/*.{js,ts,jsx,tsx,py,java,go,rs,cpp,c}']).describe('File patterns to include'), exclude_patterns: z.array(z.string()).optional().default(['**/node_modules/**', '**/dist/**', '**/*.min.js']).describe('Patterns to exclude'), max_depth: z.number().optional().default(10).describe('Maximum directory depth to analyze'), }, }, async (params: { directory: string; include_patterns?: string[]; exclude_patterns?: string[]; max_depth?: number; }) => { try { const { directory, include_patterns = ['**/*.{js,ts,jsx,tsx,py,java,go,rs,cpp,c}'], exclude_patterns = ['**/node_modules/**', '**/dist/**', '**/*.min.js'], max_depth = 10 } = params; if (!(await fs.pathExists(directory))) { return { content: [{ type: 'text', text: `Error: Directory does not exist: ${directory}` }], isError: true, }; } const analysis: CodeAnalysis = { fileCount: 0, totalLines: 0, totalCharacters: 0, languageBreakdown: {}, complexityScore: 0, issues: [], }; // Simple file extension to language mapping const languageMap: { [key: string]: string } = { '.js': 'JavaScript', '.ts': 'TypeScript', '.jsx': 'React JSX', '.tsx': 'React TSX', '.py': 'Python', '.java': 'Java', '.go': 'Go', '.rs': 'Rust', '.cpp': 'C++', '.c': 'C', '.php': 'PHP', '.rb': 'Ruby', '.swift': 'Swift', '.kt': 'Kotlin', '.scala': 'Scala', '.clj': 'Clojure', '.ex': 'Elixir', '.exs': 'Elixir', }; // Analyze files recursively const analyzeDirectory = async (dir: string, depth: number = 0): Promise<void> => { if (depth > max_depth) return; try { const items = await fs.readdir(dir, { withFileTypes: true }); for (const item of items) { const itemPath = path.join(dir, item.name); // Skip excluded patterns const isExcluded = exclude_patterns.some(pattern => { const regex = new RegExp(pattern.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*')); return regex.test(itemPath.replace(directory, '')); }); if (isExcluded) continue; if (item.isDirectory()) { await analyzeDirectory(itemPath, depth + 1); } else if (item.isFile()) { const ext = path.extname(item.name).toLowerCase(); // Check if file matches include patterns const isIncluded = include_patterns.some(pattern => { const regex = new RegExp(pattern.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*').replace(/\./g, '\\.')); return regex.test(itemPath); }); if (!isIncluded) continue; try { const content = await fs.readFile(itemPath, 'utf-8'); const lines = content.split('\n'); const lineCount = lines.length; const charCount = content.length; analysis.fileCount++; analysis.totalLines += lineCount; analysis.totalCharacters += charCount; const language = languageMap[ext] || 'Other'; analysis.languageBreakdown[language] = (analysis.languageBreakdown[language] || 0) + lineCount; // Simple complexity analysis const functionCount = (content.match(/function\s+\w+|const\s+\w+\s*=\s*\([^)]*\)\s*=>|def\s+\w+|class\s+\w+/g) || []).length; const commentCount = (content.match(/\/\/.*$|\/\*[\s\S]*?\*\/|#[^!].*$/gm) || []).length; const blankLines = lines.filter(line => line.trim() === '').length; // Calculate complexity score (simple heuristic) const codeLines = lineCount - commentCount - blankLines; const complexity = Math.max(1, Math.min(10, (functionCount * 0.1) + (codeLines / 100))); analysis.complexityScore += complexity; // Check for potential issues if (lineCount > 500) { analysis.issues.push(`${itemPath}: Large file (${lineCount} lines)`); } if (functionCount > 20) { analysis.issues.push(`${itemPath}: High function count (${functionCount})`); } if (lines.some(line => line.length > 120)) { analysis.issues.push(`${itemPath}: Lines longer than 120 characters`); } } catch (error) { analysis.issues.push(`${itemPath}: Could not read file`); } } } } catch (error) { analysis.issues.push(`${dir}: Could not read directory`); } }; await analyzeDirectory(directory); // Calculate average complexity const avgComplexity = analysis.fileCount > 0 ? (analysis.complexityScore / analysis.fileCount).toFixed(2) : '0'; let output = `# Code Analysis Report ## Summary - **Total Files**: ${analysis.fileCount} - **Total Lines**: ${analysis.totalLines.toLocaleString()} - **Total Characters**: ${analysis.totalCharacters.toLocaleString()} - **Average Complexity**: ${avgComplexity}/10 - **Issues Found**: ${analysis.issues.length} ## Language Breakdown `; for (const [language, lines] of Object.entries(analysis.languageBreakdown)) { output += `- ${language}: ${lines.toLocaleString()} lines\n`; } if (analysis.issues.length > 0) { output += '\n## Issues Found\n'; analysis.issues.forEach(issue => { output += `- ${issue}\n`; }); } return { content: [{ type: 'text', text: output }], }; } catch (error: any) { return { content: [{ type: 'text', text: `Error analyzing code: ${error.message}` }], isError: true, }; } } ); }

Latest Blog Posts

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/Yussefgafer/MyMCP'

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