Skip to main content
Glama

frontend_insights

Analyze Next.js/React project architecture to map routes, components, data flow, and identify potential issues using semantic embedding analysis.

Instructions

πŸ” Map routes, components, data flow, design system, and risks in the web layer with embedding-enhanced analysis. Analyzes Next.js/React projects for architecture insights, component similarities, and potential issues using semantic embeddings.

Input Schema

NameRequiredDescriptionDefault
projectPathYesAbsolute or relative path to the Next.js project directory
formatNoOutput format for the analysis resultsstructured
includeContentNoInclude detailed file content analysis
subtreeNoFrontend directory path to analyze (default: web/app)web/app
maxFilesNoMaximum number of files to analyze
useEmbeddingsNoEnable embedding-based similarity analysis for enhanced insights
embeddingSimilarityThresholdNoSimilarity threshold for embedding-based matches (lower = more results, higher = more precise)
maxSimilarComponentsNoMaximum number of similar components to analyze per component
analyzePatternsNoEnable pattern detection for code smells, anti-patterns, and security issues
generateEmbeddingsIfMissingNoGenerate embeddings for project files if they don't exist (may take time for large projects)

Input Schema (JSON Schema)

{ "properties": { "analyzePatterns": { "default": true, "description": "Enable pattern detection for code smells, anti-patterns, and security issues", "type": "boolean" }, "embeddingSimilarityThreshold": { "default": 0.3, "description": "Similarity threshold for embedding-based matches (lower = more results, higher = more precise)", "maximum": 1, "minimum": 0, "type": "number" }, "format": { "default": "structured", "description": "Output format for the analysis results", "enum": [ "structured", "json", "compact", "markdown" ], "type": "string" }, "generateEmbeddingsIfMissing": { "default": false, "description": "Generate embeddings for project files if they don't exist (may take time for large projects)", "type": "boolean" }, "includeContent": { "default": true, "description": "Include detailed file content analysis", "type": "boolean" }, "maxFiles": { "default": 2000, "description": "Maximum number of files to analyze", "maximum": 10000, "minimum": 1, "type": "number" }, "maxSimilarComponents": { "default": 5, "description": "Maximum number of similar components to analyze per component", "maximum": 20, "minimum": 1, "type": "number" }, "projectPath": { "description": "Absolute or relative path to the Next.js project directory", "type": "string" }, "subtree": { "default": "web/app", "description": "Frontend directory path to analyze (default: web/app)", "type": "string" }, "useEmbeddings": { "default": true, "description": "Enable embedding-based similarity analysis for enhanced insights", "type": "boolean" } }, "required": [ "projectPath" ], "type": "object" }

Implementation Reference

  • Core handler function that orchestrates frontend insights analysis: file discovery, frontend file filtering, auto-detection of app/pages dir, route/component/data flow analysis, risk detection, and formatted output.
    async function handleFrontendInsights(args: any): Promise<any> { const { projectPath, format = 'structured', subtree = 'web/app', maxFiles = 2000 } = args; // Validate that projectPath is provided if (!projectPath) { throw new Error( '❌ projectPath is required. Please provide an absolute path to the Next.js project directory.' ); } const resolvedProjectPath = validateAndResolvePath(projectPath); logger.info('πŸ” Starting frontend insights analysis', { originalPath: projectPath, resolvedPath: resolvedProjectPath, format, subtree, maxFiles, }); try { // Discover files in the project const fileDiscovery = new FileDiscovery(resolvedProjectPath); const allFiles = await fileDiscovery.discoverFiles(); // Filter files for frontend types (including HTML, CSS, config files, and other relevant files) const frontendFiles = allFiles.filter(file => { const isFrontendCode = /\.(ts|tsx|js|jsx|vue|svelte|html|css|scss|sass|less|astro|mdx)$/.test( file.relPath ); const isConfigFile = file.relPath.includes('.config.') || file.relPath.includes('config.'); const isExcluded = file.relPath.includes('node_modules') || file.relPath.includes('dist') || file.relPath.includes('.next') || file.relPath.includes('build'); return (isFrontendCode || isConfigFile) && !isExcluded; }); logger.info(`πŸ“ Found ${frontendFiles.length} frontend files (${allFiles.length} total)`); // Analyze file composition const fileComposition = analyzeFileComposition(allFiles, frontendFiles); // Auto-detect the correct app directory if subtree is default or doesn't exist let effectiveSubtree = subtree; if (subtree === 'web/app' || subtree === 'app') { // Check for common app directory patterns const appPatterns = ['app', 'src/app', 'web/app', 'pages', 'src/pages']; let detectedAppDir = null; for (const pattern of appPatterns) { const testPath = path.join(resolvedProjectPath, pattern); const filesInPattern = frontendFiles.filter(file => file.absPath.startsWith(testPath)); if (filesInPattern.length > 0) { // Check if there are actual page files in this directory // For App Router: look for /page. files // For Pages Router: look for any JS/TS/JSX/TSX files (not special App Router files) const pageFiles = filesInPattern.filter(file => { const relPath = file.relPath; if (pattern.includes('pages')) { // Pages Router: any JS/TS file that's not an API route or special file return ( /\.(js|jsx|ts|tsx)$/.test(relPath) && !relPath.includes('/api/') && !relPath.includes('/_') && !relPath.includes('/page.') && !relPath.includes('/layout.') && !relPath.includes('/route.') ); } else { // App Router: look for /page. files return relPath.includes('/page.'); } }); if (pageFiles.length > 0) { detectedAppDir = pattern; logger.info( `πŸ” Auto-detected ${pattern.includes('pages') ? 'Pages Router' : 'App Router'} directory: ${pattern} (${pageFiles.length} page files found)` ); break; } } } if (detectedAppDir) { effectiveSubtree = detectedAppDir; } else { // If no specific app directory found, use the entire project but prioritize app-like structures effectiveSubtree = '.'; logger.info(`πŸ” No specific app directory detected, analyzing entire project`); } } // Filter files for the effective subtree const targetPath = path.join(resolvedProjectPath, effectiveSubtree); const subtreeFiles = frontendFiles.filter( file => effectiveSubtree === '.' || file.absPath.startsWith(targetPath) ); const filesToAnalyze = subtreeFiles.length > 0 ? subtreeFiles.slice(0, maxFiles) : frontendFiles.slice(0, maxFiles); logger.info(`πŸ“ Analyzing ${filesToAnalyze.length} files in frontend`); // Initialize basic analysis results const insights: FrontendInsights = { generatedAt: new Date().toISOString(), summary: { pages: 0, clientComponents: 0, serverComponents: 0, stateStores: [], dataLibraries: [], designSystem: [], fileComposition, }, routes: { pages: [], handlers: [], }, boundaries: [], components: [], dataFlow: { endpoints: [], externalBases: [], endpointCalls: [], duplicateEndpoints: [], }, env: { nextPublic: [], clientLeaks: [], leaks: [], }, performance: { heavyClientImports: [], noDynamicCandidates: [], }, accessibility: [], risks: { score: 0, trustedScore: 0, rules: [], }, recommendedNextSteps: [], }; // Basic analysis try { // Analyze routes - pass the detected app directory logger.info('πŸ›£οΈ Analyzing routes'); const appDir = effectiveSubtree === '.' ? 'app' : effectiveSubtree; // Use detected directory or default to 'app' const routeAnalysis = await analyzeRoutes(filesToAnalyze, appDir); let totalPages = 0; // Handle the case where routeAnalysis might be an array or have a different structure if (Array.isArray(routeAnalysis)) { insights.routes = { pages: routeAnalysis as any, handlers: [] }; // Count only routes that have page files (App Router) const appRouterPages = routeAnalysis.filter((route: any) => route.files?.page).length; totalPages += appRouterPages; } else if (routeAnalysis && typeof routeAnalysis === 'object') { insights.routes = routeAnalysis as any; totalPages += (routeAnalysis as any).pages?.length || 0; } else { insights.routes = { pages: [], handlers: [] }; } // Count Pages Router pages and HTML files const pagesRouterPages = filesToAnalyze.filter(file => { const relPath = file.relPath.replace(/\\/g, '/'); // Pages Router: files in pages/ or src/pages/ that are JS/TS/JSX/TSX (but not API routes or special App Router files) const isInPagesDir = relPath.includes('/pages/') || relPath.startsWith('pages/') || relPath.startsWith('src/pages/'); if (isInPagesDir) { return ( /\.(js|jsx|ts|tsx)$/.test(relPath) && !relPath.includes('/api/') && !relPath.includes('/_') && !relPath.includes('/page.') && !relPath.includes('/layout.') && !relPath.includes('/route.') ); } return false; }).length; // Count HTML pages const htmlPages = filesToAnalyze.filter( file => file.relPath.endsWith('.html') && !file.relPath.includes('node_modules') && !file.relPath.includes('dist') && !file.relPath.includes('.next') ).length; totalPages += pagesRouterPages + htmlPages; if (pagesRouterPages > 0) { logger.info(`πŸ“„ Found ${pagesRouterPages} Pages Router pages`); } if (htmlPages > 0) { logger.info(`πŸ“„ Found ${htmlPages} HTML pages`); } insights.summary.pages = totalPages; } catch (error) { logger.warn('Route analysis failed:', { error: error instanceof Error ? error.message : String(error), }); insights.routes = { pages: [], handlers: [] }; } try { // Analyze components logger.info('βš›οΈ Analyzing components'); const componentAnalysis = await analyzeComponents(filesToAnalyze); insights.components = Array.isArray(componentAnalysis) ? componentAnalysis : []; insights.summary.clientComponents = insights.components.filter( (c: any) => c.kind === 'client' ).length; insights.summary.serverComponents = insights.components.filter( (c: any) => c.kind === 'server' ).length; } catch (error) { logger.warn('Component analysis failed:', { error: error instanceof Error ? error.message : String(error), }); insights.components = []; } try { // Analyze data flow logger.info('πŸ”„ Analyzing data flow'); const dataFlowAnalysis = await analyzeDataFlow(filesToAnalyze, insights.components); insights.dataFlow.endpoints = (dataFlowAnalysis.endpoints || []).map((e: any) => ({ method: e.method || 'GET', path: e.path, usedBy: e.usedBy, })); insights.dataFlow.endpointCalls = dataFlowAnalysis.endpointCalls || []; insights.dataFlow.duplicateEndpoints = dataFlowAnalysis.duplicateEndpoints || []; } catch (error) { logger.warn('Data flow analysis failed:', { error: error instanceof Error ? error.message : String(error), }); insights.dataFlow = { endpoints: [], externalBases: [], endpointCalls: [], duplicateEndpoints: [], }; } // Generate simple recommended next steps const nextSteps = []; if (insights.dataFlow.duplicateEndpoints.length > 3) { nextSteps.push({ title: `Consolidate ${insights.dataFlow.duplicateEndpoints.length} duplicate API calls`, }); } if (insights.components.length > 50) { nextSteps.push({ title: `Review component architecture (${insights.components.length} components found)`, }); } if (insights.dataFlow.endpoints.length > 20) { nextSteps.push({ title: `Consider API consolidation (${insights.dataFlow.endpoints.length} endpoints found)`, }); } insights.recommendedNextSteps = nextSteps; logger.info('βœ… Frontend insights analysis complete', { pages: insights.summary.pages, components: insights.components.length, endpoints: insights.dataFlow.endpoints.length, }); // Format and return results const formattedResult = formatFrontendInsights(insights, format); return { content: [ { type: 'text', text: formattedResult, }, ], }; } catch (error) { logger.error('Failed to analyze frontend insights:', { error: error instanceof Error ? error.message : String(error), }); throw new Error( `Frontend insights analysis failed: ${error instanceof Error ? error.message : String(error)}` ); } }
  • Tool definition including name 'frontend_insights', description, and detailed JSON inputSchema for MCP protocol compliance.
    const frontendInsightsTool = { name: 'frontend_insights', description: 'πŸ” Map routes, components, data flow, design system, and risks in the web layer with embedding-enhanced analysis. Analyzes Next.js/React projects for architecture insights, component similarities, and potential issues using semantic embeddings.', inputSchema: { type: 'object', properties: { projectPath: { type: 'string', description: 'Absolute or relative path to the Next.js project directory', }, format: { type: 'string', enum: ['structured', 'json', 'compact', 'markdown'], default: 'structured', description: 'Output format for the analysis results', }, includeContent: { type: 'boolean', default: true, description: 'Include detailed file content analysis', }, subtree: { type: 'string', default: 'web/app', description: 'Frontend directory path to analyze (default: web/app)', }, maxFiles: { type: 'number', default: 2000, minimum: 1, maximum: 10000, description: 'Maximum number of files to analyze', }, useEmbeddings: { type: 'boolean', default: true, description: 'Enable embedding-based similarity analysis for enhanced insights', }, embeddingSimilarityThreshold: { type: 'number', default: 0.3, minimum: 0.0, maximum: 1.0, description: 'Similarity threshold for embedding-based matches (lower = more results, higher = more precise)', }, maxSimilarComponents: { type: 'number', default: 5, minimum: 1, maximum: 20, description: 'Maximum number of similar components to analyze per component', }, analyzePatterns: { type: 'boolean', default: true, description: 'Enable pattern detection for code smells, anti-patterns, and security issues', }, generateEmbeddingsIfMissing: { type: 'boolean', default: false, description: "Generate embeddings for project files if they don't exist (may take time for large projects)", }, }, required: ['projectPath'], }, };
  • src/index.ts:126-141 (registration)
    Registration of 'frontendInsightsTool' in MCP server.tools array and 'handleFrontendInsights' mapped to 'frontend_insights' in server.handlers dictionary.
    this.tools = [ ...(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,
  • Helper function to format FrontendInsights results in structured, JSON, compact, or markdown formats; called by handler.
    export function formatFrontendInsights( data: FrontendInsights, format: 'structured' | 'json' | 'compact' | 'markdown' ): string { switch (format) { case 'json': return JSON.stringify(data, null, 2); case 'compact': return formatCompactFrontendInsights(data); case 'markdown': return formatMarkdownFrontendInsights(data); case 'structured': default: return formatStructuredFrontendInsights(data); } }
  • Internal Zod schema for runtime validation of tool input parameters.
    const FRONTEND_INSIGHTS_SCHEMA = z.object({ projectPath: z.string().describe('Absolute or relative path to the Next.js project directory'), format: z .enum(['structured', 'json', 'compact', 'markdown']) .default('structured') .describe('Output format for the analysis results'), includeContent: z.boolean().default(true).describe('Include detailed file content analysis'), subtree: z .string() .default('web/app') .describe('Frontend directory path to analyze (default: web/app)'), maxFiles: z .number() .min(1) .max(10000) .default(2000) .describe('Maximum number of files to analyze'), useEmbeddings: z .boolean() .default(true) .describe('Enable embedding-based similarity analysis for enhanced insights'), embeddingSimilarityThreshold: z .number() .min(0.0) .max(1.0) .default(0.3) .describe( 'Similarity threshold for embedding-based matches (lower = more results, higher = more precise)' ), maxSimilarComponents: z .number() .min(1) .max(20) .default(5) .describe('Maximum number of similar components to analyze per component'), analyzePatterns: z .boolean() .default(true) .describe('Enable pattern detection for code smells, anti-patterns, and security issues'), generateEmbeddingsIfMissing: z .boolean() .default(false) .describe( "Generate embeddings for project files if they don't exist (may take time for large projects)" ), });

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