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
| Name | Required | Description | Default |
|---|---|---|---|
| projectPath | Yes | Absolute or relative path to the Next.js project directory | |
| format | No | Output format for the analysis results | structured |
| includeContent | No | Include detailed file content analysis | |
| subtree | No | Frontend directory path to analyze (default: web/app) | web/app |
| maxFiles | No | Maximum number of files to analyze | |
| useEmbeddings | No | Enable embedding-based similarity analysis for enhanced insights | |
| embeddingSimilarityThreshold | No | Similarity threshold for embedding-based matches (lower = more results, higher = more precise) | |
| maxSimilarComponents | No | Maximum number of similar components to analyze per component | |
| analyzePatterns | No | Enable pattern detection for code smells, anti-patterns, and security issues | |
| generateEmbeddingsIfMissing | No | Generate 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)" ), });