Skip to main content
Glama

local_file_summary

Analyze code files to generate AST-based summaries and identify key symbols for quick understanding of file structure and contents.

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

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

Input Schema (JSON Schema)

{ "properties": { "filePath": { "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\".", "type": "string" }, "format": { "default": "structured", "description": "Output format preference", "enum": [ "xml", "structured", "compact" ], "type": "string" }, "includeSymbols": { "default": true, "description": "Include detailed symbol information", "type": "boolean" }, "maxSymbols": { "default": 20, "description": "Maximum number of symbols to return", "maximum": 50, "minimum": 5, "type": "number" } }, "required": [ "filePath" ], "type": "object" }

Implementation Reference

  • Core handler function that implements the 'local_file_summary' tool. Validates input path, performs AST parsing using SemanticCompactor and ASTParser, extracts functions/classes/interfaces/symbols, calculates complexity, handles non-code files, and returns formatted summary.
    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 inputSchema defining parameters: filePath (required), includeSymbols, maxSymbols, 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'], }, };
  • Registration of the handler in the localHandlers object mapping 'local_file_summary' to handleFileSummary function.
    export const localHandlers = { ...(allowLocalContext ? { local_context: handleSemanticCompact } : {}), local_project_hints: handleProjectHints, local_file_summary: handleFileSummary, frontend_insights: handleFrontendInsights, local_debug_context: handleLocalDebugContext, manage_embeddings: handleManageEmbeddings, ast_grep_search: handleAstGrep, };
  • src/index.ts:135-142 (registration)
    Final MCP server registration in AmbianceMCPServer constructor, adding to this.handlers map used by setupToolHandlers for MCP protocol.
    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, };
  • Key helper function for AST analysis, extracts functions, classes, interfaces, symbols; used by handler; includes ast-grep fallback for Python, Go, etc.
    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