Skip to main content
Glama

local_file_summary

Analyze file structure and extract key symbols using AST parsing to provide code summaries without external dependencies. Accepts absolute or relative file paths.

Instructions

📄 Get quick AST-based summary and key symbols for any file. Fast file analysis without external dependencies. Accepts absolute paths or relative paths (when workspace can be detected).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
filePathYesFile path for analysis. Can be absolute (recommended) or relative to workspace. Examples: "C:\Dev\my-project\src\index.ts", "/Users/username/project/src/index.ts", or "src/index.ts".
includeSymbolsNoInclude detailed symbol information
maxSymbolsNoMaximum number of symbols to return
formatNoOutput format preferencestructured

Implementation Reference

  • Core handler function for 'local_file_summary' tool. Validates input, resolves file path, performs comprehensive AST analysis, extracts functions/classes/interfaces/symbols, calculates cyclomatic complexity, handles non-code files, and formats output in requested style.
    export async function handleFileSummary(args: any): Promise<any> { const { filePath, includeSymbols = true, maxSymbols = 20, format = 'structured' } = args; logger.info('🔍 handleFileSummary called with', { filePath, argsKeys: Object.keys(args), args: args, }); // Validate that filePath is provided and is absolute if (!filePath) { throw new Error('❌ filePath is required. Please provide an absolute path to the file.'); } const resolvedFilePath = validateAndResolvePath(filePath); const projectPath = path.dirname(resolvedFilePath); logger.info('📄 Analyzing file', { originalPath: filePath, resolvedPath: resolvedFilePath, projectPath, includeSymbols, maxSymbols, }); try { // Check if file exists before proceeding const fs = await import('fs/promises'); try { await fs.access(resolvedFilePath); } catch (error) { throw new Error('File not found'); } const languageInfo = getLanguageFromPath(filePath); const language = languageInfo.lang; // Check if this is a non-code file that doesn't need AST analysis if ( language === 'json' || language === 'markdown' || language === 'yaml' || language === 'toml' || language === 'text' ) { return await handleNonCodeFile( resolvedFilePath, language, format, includeSymbols, maxSymbols ); } // Use semantic compactor for single file analysis (optional) let nodes = []; try { const compactor = new SemanticCompactor(projectPath); nodes = await compactor.getSummary(resolvedFilePath); compactor.dispose(); } catch (error) { logger.warn('Skipping semantic compactor summary for single file', { projectPath, error: error instanceof Error ? error.message : String(error), }); nodes = []; } // 🔑 Get comprehensive analysis directly from AST (bypass semantic compactor filtering) logger.info('🔍 About to call getComprehensiveASTAnalysis', { resolvedFilePath }); let astAnalysis; try { astAnalysis = await getComprehensiveASTAnalysis(resolvedFilePath); } catch (error) { logger.error('❌ getComprehensiveASTAnalysis failed', { error: error instanceof Error ? error.message : String(error), filePath: resolvedFilePath, }); throw error; } if (!astAnalysis) { logger.error('❌ getComprehensiveASTAnalysis returned undefined', { resolvedFilePath }); throw new Error('AST analysis returned undefined'); } logger.info('✅ Got AST analysis', { totalSymbols: astAnalysis?.totalSymbols, hasAllFunctions: !!astAnalysis?.allFunctions, hasAllClasses: !!astAnalysis?.allClasses, hasAllInterfaces: !!astAnalysis?.allInterfaces, }); // Get semantic compactor results for comparison (but don't rely on them for symbol count) // Debug: Log the AST analysis results logger.debug('🔍 AST Analysis Results', { filePath: resolvedFilePath, totalSymbols: astAnalysis.totalSymbols, functions: astAnalysis.allFunctions.length, classes: astAnalysis.allClasses.length, interfaces: astAnalysis.allInterfaces.length, exportedSymbols: astAnalysis.exportedSymbols.length, topSymbols: astAnalysis.topSymbols.length, sampleFunctions: astAnalysis.allFunctions .slice(0, 3) .map(f => ({ name: f.name, type: f.type, line: f.line })), sampleClasses: astAnalysis.allClasses .slice(0, 3) .map(c => ({ name: c.name, type: c.type, line: c.line })), }); // Force use the actual AST analysis results instead of semantic compactor logger.info('🔧 Using AST analysis results directly', { symbolCount: astAnalysis.totalSymbols, functions: astAnalysis.allFunctions.length, classes: astAnalysis.allClasses.length, }); // Extract file header information const fileHeader = await extractFileHeader(resolvedFilePath); // Calculate cyclomatic complexity let complexityData = { rating: 'low', description: 'Simple code', totalComplexity: 1, decisionPoints: 0, breakdown: {}, }; try { const fs = await import('fs'); const fileContent = await fs.promises.readFile(resolvedFilePath, 'utf8'); complexityData = calculateCyclomaticComplexity(fileContent); } catch (error) { // Fallback to simple symbol-based complexity if file reading fails complexityData.rating = astAnalysis.totalSymbols > 50 ? 'high' : astAnalysis.totalSymbols > 20 ? 'medium' : 'low'; complexityData.description = `Based on ${astAnalysis.totalSymbols} symbols`; } // Clean up resources // compactor.dispose(); // Moved inside try-catch const summary = { file: filePath, exists: astAnalysis.totalSymbols > 0, symbolCount: astAnalysis.totalSymbols, // 🔑 Use actual AST symbol count fileHeader: fileHeader, allFunctions: astAnalysis.allFunctions, exportedSymbols: astAnalysis.exportedSymbols, allClasses: astAnalysis.allClasses, // 🔑 Add class information allInterfaces: astAnalysis.allInterfaces, // 🔑 Add interface information symbols: includeSymbols ? astAnalysis.topSymbols.slice(0, maxSymbols) // 🔑 Use better symbol selection : [], complexity: complexityData.rating, complexityData: complexityData, // 🔑 Add detailed complexity information language, }; const quickAnalysis = generateQuickFileAnalysis(summary); // Format the output based on preference let formattedSummary: string; try { formattedSummary = formatFileSummaryOutput(summary, quickAnalysis, format); if (typeof formattedSummary !== 'string') { logger.warn('Formatter returned non-string value', { type: typeof formattedSummary, value: formattedSummary, }); formattedSummary = `Error: Formatter returned ${typeof formattedSummary} instead of string`; } } catch (error) { logger.error('Failed to format file summary', { error: error instanceof Error ? error.message : String(error), }); formattedSummary = `Error formatting summary: ${error instanceof Error ? error.message : String(error)}`; } logger.info('✅ File analysis completed', { symbolCount: summary.symbolCount, complexity: summary.complexity, }); return { success: true, summary: formattedSummary, quickAnalysis, metadata: { format, symbolCount: summary.symbolCount, complexity: summary.complexity, language: summary.language, }, usage: `Found ${summary.symbolCount} symbols with ${summary.complexity} complexity`, }; } catch (error) { logger.error('❌ File analysis failed', { filePath, error: error instanceof Error ? error.message : String(error), }); return { success: false, error: error instanceof Error ? error.message : String(error), fallback: `Could not analyze ${filePath}. File may not exist, be too large, or contain unsupported language.`, suggestion: 'Try local_project_hints to understand the overall project structure instead.', }; } }
  • Tool schema definition for 'local_file_summary' including name, description, and detailed inputSchema with parameters for filePath, symbols, limits, and output format.
    export const localFileSummaryTool = { name: 'local_file_summary', description: '📄 Get quick AST-based summary and key symbols for any file. Fast file analysis without external dependencies. Accepts absolute paths or relative paths (when workspace can be detected).', inputSchema: { type: 'object', properties: { filePath: { type: 'string', description: 'File path for analysis. Can be absolute (recommended) or relative to workspace. Examples: "C:\\Dev\\my-project\\src\\index.ts", "/Users/username/project/src/index.ts", or "src/index.ts".', }, includeSymbols: { type: 'boolean', default: true, description: 'Include detailed symbol information', }, maxSymbols: { type: 'number', default: 20, minimum: 5, maximum: 50, description: 'Maximum number of symbols to return', }, format: { type: 'string', enum: ['xml', 'structured', 'compact'], default: 'structured', description: 'Output format preference', }, }, required: ['filePath'], }, };
  • src/index.ts:127-142 (registration)
    MCP server registration: adds localFileSummaryTool to this.tools[] list (returned by list tools) and maps 'local_file_summary' to handleFileSummary in this.handlers (used by call tool dispatcher).
    ...(allowLocalContext ? [localSemanticCompactTool] : []), localProjectHintsTool, localFileSummaryTool, frontendInsightsTool, localDebugContextTool, astGrepTool, ]; this.handlers = { ...(allowLocalContext ? { local_context: handleSemanticCompact } : {}), local_project_hints: handleProjectHints, local_file_summary: handleFileSummary, frontend_insights: handleFrontendInsights, local_debug_context: handleLocalDebugContext, ast_grep_search: handleAstGrep, };
  • Intermediate registration in localHandlers map, which is imported and used by src/index.ts and src/tools/index.ts.
    local_project_hints: handleProjectHints, local_file_summary: handleFileSummary, frontend_insights: handleFrontendInsights, local_debug_context: handleLocalDebugContext, manage_embeddings: handleManageEmbeddings, ast_grep_search: handleAstGrep, };
  • Key helper function for comprehensive AST-based symbol extraction used by the handler, supporting multiple languages with ast-grep fallback.
    export async function getComprehensiveASTAnalysis(filePath: string): Promise<{ totalSymbols: number; allFunctions: any[]; allClasses: any[]; allInterfaces: any[]; exportedSymbols: string[]; topSymbols: any[]; }> { try { logger.info('🔍 Starting AST analysis', { filePath }); const languageInfo = getLanguageFromPath(filePath); const language = languageInfo.lang as any; const grepLang = languageInfo.grep; logger.info('📝 Detected language', { language, grepLang }); const parser = new ASTParser(); logger.info('⚙️ Created AST parser, about to parse file'); let parsedFile = await parser.parseFile(filePath, language); // Handle undefined/null from parser for unsupported langs (e.g., Python/Go no grammar) if (!parsedFile) { logger.warn('ASTParser returned undefined, forcing fallback', { filePath, language }); parsedFile = { symbols: [], imports: [], exports: [], errors: [], absPath: filePath, language, }; } logger.info('✅ Parsed file successfully', { hasSymbols: !!parsedFile?.symbols, symbolCount: parsedFile?.symbols?.length || 0, }); let allFunctions: any[] = []; let allClasses: any[] = []; const allInterfaces: any[] = []; let exportedSymbols: string[] = []; // Existing JS/TS/Python processing for (const symbol of parsedFile.symbols) { if (symbol.type === 'function') { allFunctions.push({ name: symbol.name, signature: resolveTypeFromAST(symbol.signature), line: symbol.startLine, isAsync: symbol.isAsync || symbol.signature.includes('async'), isExported: symbol.isExported, isMethod: false, className: undefined, parameters: extractParametersFromSignature(symbol.signature), returnType: resolveTypeFromAST(symbol.returnType || ''), returnedSymbols: extractReturnedSymbols(symbol.body), purpose: 'Function', }); } else if (symbol.type === 'method') { // Handle individual method symbols with full signatures allFunctions.push({ name: symbol.name, signature: resolveTypeFromAST(symbol.signature), line: symbol.startLine, isAsync: symbol.isAsync || false, isExported: symbol.isExported, isMethod: true, className: symbol.className, parameters: symbol.parameters || extractParametersFromSignature(symbol.signature), returnType: resolveTypeFromAST(symbol.returnType || ''), returnedSymbols: extractReturnedSymbols(symbol.body), purpose: 'Method', }); } else if (symbol.type === 'class') { allClasses.push({ name: symbol.name, line: symbol.startLine, isExported: symbol.isExported, signature: resolveTypeFromAST(symbol.signature), methods: symbol.body && symbol.body.startsWith('Methods:') ? symbol.body .replace('Methods:', '') .trim() .split(',') .map(m => m.trim()) .filter(m => m && m !== 'constructor') : [], }); } else if (symbol.type === 'interface') { allInterfaces.push({ name: symbol.name, line: symbol.startLine, signature: resolveTypeFromAST(symbol.signature), purpose: 'Type definition', }); } // Collect exported symbols if (symbol.isExported && !exportedSymbols.includes(symbol.name)) { exportedSymbols.push(symbol.name); } } // Close the for loop // Add exports from export statements if (parsedFile.exports) { parsedFile.exports.forEach(exportStmt => { if (exportStmt.name && !exportedSymbols.includes(exportStmt.name)) { exportedSymbols.push(exportStmt.name); } }); } // Fallback to ast-grep if few/no symbols or non-JS/TS (force for samples/unsupported) let initialSymbolCount = parsedFile.symbols.length || 0; const isNonJsTs = grepLang && !['ts', 'js', 'jsx', 'tsx'].includes(grepLang); if ((initialSymbolCount < 2 || isNonJsTs) && grepLang) { logger.info('🔄 Falling back to ast-grep for symbol extraction', { filePath, grepLang, initialSymbolCount, }); const astGrepSymbols = extractSymbolsWithAstGrep(filePath, grepLang); allFunctions = [...allFunctions, ...astGrepSymbols.functions]; allClasses = [...allClasses, ...astGrepSymbols.classes]; exportedSymbols = [...exportedSymbols, ...(astGrepSymbols.exports || [])]; logger.info('✅ Ast-grep extraction complete', { functions: astGrepSymbols.functions.length, classes: astGrepSymbols.classes.length, }); initialSymbolCount += astGrepSymbols.functions.length + astGrepSymbols.classes.length; } // Create top symbols list (prioritize important symbols) const topSymbols: any[] = []; // Add classes first (most important) allClasses.forEach(cls => { topSymbols.push({ name: cls.name, type: 'class', line: cls.line, purpose: 'Core class', signature: resolveTypeFromAST(cls.signature).substring(0, 100) + (cls.signature.length > 100 ? '...' : ''), }); }); // Add interfaces allInterfaces.forEach(iface => { topSymbols.push({ name: iface.name, type: 'interface', line: iface.line, purpose: iface.purpose, signature: resolveTypeFromAST(iface.signature).substring(0, 100) + (iface.signature.length > 100 ? '...' : ''), }); }); // Add some key functions (exported first) const keyFunctions = allFunctions .filter(f => !f.isMethod) // Only standalone functions .sort((a, b) => (b.isExported ? 1 : 0) - (a.isExported ? 1 : 0)) .slice(0, 5); keyFunctions.forEach(func => { topSymbols.push({ name: func.name, type: 'function', line: func.line, purpose: func.purpose, signature: resolveTypeFromAST(func.signature).substring(0, 100) + (func.signature.length > 100 ? '...' : ''), }); }); parser.dispose(); return { totalSymbols: initialSymbolCount, // Use updated count after fallback allFunctions, allClasses, allInterfaces, exportedSymbols, topSymbols, }; } catch (error) { logger.warn('Failed to get comprehensive AST analysis', { filePath, error: error instanceof Error ? error.message : String(error), }); return { totalSymbols: 0, allFunctions: [], allClasses: [], allInterfaces: [], exportedSymbols: [], topSymbols: [], }; } }

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/sbarron/AmbianceMCP'

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