ai-debug-local-mcp
Version:
🎯 ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh
262 lines • 12.5 kB
JavaScript
import { BaseToolHandler } from '../base-handler.js';
import { formatBundleSize, formatNoBundlesMessage } from './utils/performance-formatters.js';
/**
* Handler for Next.js performance analysis tools (data fetching, bundles, components)
*/
export class NextJSPerformanceHandler extends BaseToolHandler {
nextjsEngine;
constructor(nextjsEngine) {
super();
this.nextjsEngine = nextjsEngine;
}
tools = [
{
name: 'nextjs_data_fetching',
description: 'Analyze Next.js data fetching patterns including cache status, waterfalls, and optimization opportunities.',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'Debug session ID'
}
},
required: ['sessionId']
}
},
{
name: 'nextjs_bundle_analyze',
description: 'Analyze Next.js bundle sizes, code splitting effectiveness, and identify unused dependencies.',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'Debug session ID'
}
},
required: ['sessionId']
}
},
{
name: 'nextjs_server_components',
description: 'Analyze Server/Client component boundaries, data flow, and provide optimization recommendations.',
inputSchema: {
type: 'object',
properties: {
sessionId: {
type: 'string',
description: 'Debug session ID'
}
},
required: ['sessionId']
}
}
];
async handle(toolName, args, sessions) {
const session = this.validateSession(args.sessionId, sessions);
switch (toolName) {
case 'nextjs_data_fetching':
return this.analyzeDataFetching(args, session);
case 'nextjs_bundle_analyze':
return this.analyzeBundles(args, session);
case 'nextjs_server_components':
return this.analyzeServerComponents(args, session);
default:
throw new Error(`Unknown Next.js performance tool: ${toolName}`);
}
}
validateSession(sessionId, sessions) {
const session = sessions.get(sessionId);
if (!session) {
throw new Error(`Debug session ${sessionId} not found`);
}
if (session.framework !== 'nextjs' && session.framework !== 'next' && !(session.framework === 'react' && session.isNextJS)) {
throw new Error('This tool only works with Next.js applications');
}
return session;
}
async analyzeDataFetching(args, session) {
try {
const analysis = await this.nextjsEngine.getDataFetchingAnalysis();
let content = '📊 **Next.js Data Fetching Analysis**\n\n';
if (analysis.length === 0) {
content += 'No data fetching detected on this page.\n';
content += 'This might be a static page or all data is client-side.\n';
return this.createTextResponse(content);
}
// Analyze each data fetching pattern
analysis.forEach((pattern, index) => {
content += `### Fetch ${index + 1}: ${pattern.method}\n`;
content += `- **Cache Status:** ${pattern.cacheStatus}\n`;
if (pattern.revalidateTime) {
content += `- **Revalidate:** ${pattern.revalidateTime}s\n`;
}
if (pattern.waterfalls && pattern.waterfalls.length > 0) {
content += `- **Waterfalls Detected:**\n`;
pattern.waterfalls.forEach(waterfall => {
content += ` - ${waterfall.component} (${waterfall.fetchTime})`;
if (waterfall.blocking) {
content += ' ⚠️ Blocking';
}
content += '\n';
});
}
content += '\n';
});
// Overall recommendations
content += '### 💡 Recommendations:\n';
const hasWaterfalls = analysis.some(p => p.waterfalls && p.waterfalls.some(w => w.blocking));
if (hasWaterfalls) {
content += '- ⚠️ Sequential data fetching detected\n';
content += ' - Use Promise.all() to parallelize independent fetches\n';
content += ' - Consider React Suspense for better loading states\n';
}
return this.createTextResponse(content);
}
catch (error) {
throw new Error(`Failed to analyze data fetching: ${error instanceof Error ? error.message : String(error)}`);
}
}
async analyzeBundles(args, session) {
try {
const result = await this.nextjsEngine.analyzeBundle();
const { totalSize, gzippedSize, chunks, recommendations } = result;
// Check if no bundles detected (dev mode)
if (totalSize === 0 || !chunks || chunks.length === 0) {
return {
content: [{
type: 'text',
text: formatNoBundlesMessage()
}]
};
}
let content = '📦 **Next.js Bundle Analysis**\n\n';
// Total size
content += `**Total Bundle Size:** ${formatBundleSize(totalSize)}\n`;
content += `**Gzipped Size:** ${formatBundleSize(gzippedSize)}\n\n`;
// Chunks analysis
if (chunks && chunks.length > 0) {
content += '**Chunks by Type:**\n';
const initialChunks = chunks.filter(chunk => chunk.type === 'initial');
const asyncChunks = chunks.filter(chunk => chunk.type === 'async');
const runtimeChunks = chunks.filter(chunk => chunk.type === 'runtime');
if (initialChunks.length > 0) {
content += `\n**Initial Chunks (${initialChunks.length}):**\n`;
initialChunks
.sort((a, b) => b.size - a.size)
.slice(0, 5)
.forEach(chunk => {
const sizeKB = (chunk.size / 1024).toFixed(0);
const indicator = chunk.size > 200 * 1024 ? '⚠️' : '✅';
content += `- ${indicator} **${chunk.name}**: ${sizeKB}KB\n`;
});
}
if (asyncChunks.length > 0) {
content += `\n**Async Chunks (${asyncChunks.length}):**\n`;
asyncChunks
.sort((a, b) => b.size - a.size)
.slice(0, 3)
.forEach(chunk => {
const sizeKB = (chunk.size / 1024).toFixed(0);
content += `- **${chunk.name}**: ${sizeKB}KB\n`;
});
}
if (runtimeChunks.length > 0) {
content += `\n**Runtime Chunks (${runtimeChunks.length}):**\n`;
runtimeChunks.forEach(chunk => {
const sizeKB = (chunk.size / 1024).toFixed(0);
content += `- **${chunk.name}**: ${sizeKB}KB\n`;
});
}
content += '\n';
}
// Recommendations
if (recommendations && recommendations.length > 0) {
content += '**💡 Recommendations:**\n';
recommendations.forEach((rec) => {
content += `- ${rec}\n`;
});
}
// Additional static recommendations
content += '\n**📚 Common Dependencies & Alternatives:**\n';
content += '- **moment** (290KB) → **date-fns** (89KB) or **dayjs** (7KB)\n';
content += '- **lodash** (71KB) → **lodash-es** with tree-shaking\n';
content += '- **react-icons** → Import only needed icons\n';
return this.createTextResponse(content);
}
catch (error) {
throw new Error(`Failed to analyze Next.js bundles: ${error instanceof Error ? error.message : String(error)}`);
}
}
async analyzeServerComponents(args, session) {
try {
const analysis = await this.nextjsEngine.analyzeServerClientFlow();
let content = '🖥️ **Next.js Server Components Analysis**\n\n';
// Summary stats
content += `**Component Distribution:**\n`;
content += `- Server Components: ${analysis.serverComponents}\n`;
content += `- Client Components: ${analysis.clientComponents}\n`;
content += `- Total Components: ${analysis.serverComponents + analysis.clientComponents}\n\n`;
// Bundle splitting information
if (analysis.bundleSplitting) {
content += '**Bundle Splitting:**\n';
content += `- Server Chunks: ${analysis.bundleSplitting.serverChunks.length}\n`;
content += `- Client Chunks: ${analysis.bundleSplitting.clientChunks.length}\n`;
content += `- Shared Chunks: ${analysis.bundleSplitting.sharedChunks.length}\n\n`;
if (analysis.bundleSplitting.sharedChunks.length > 0) {
content += '**Shared Chunks:**\n';
analysis.bundleSplitting.sharedChunks.forEach(chunk => {
content += `- ${chunk}\n`;
});
content += '\n';
}
}
// Hydration mismatches
if (analysis.hydrationMismatches && analysis.hydrationMismatches.length > 0) {
content += '**⚠️ Hydration Mismatches:**\n';
analysis.hydrationMismatches.forEach(mismatch => {
content += `- ${mismatch}\n`;
});
content += '\n';
}
// Boundaries analysis
if (analysis.boundaries && analysis.boundaries.length > 0) {
content += '**🔍 Component Boundaries:**\n';
analysis.boundaries.forEach((boundary) => {
content += `- ${boundary.parent} → ${boundary.child}: ${boundary.issue}\n`;
});
content += '\n';
}
// Optimizations
if (analysis.optimizations && analysis.optimizations.length > 0) {
content += '**🚀 Optimization Opportunities:**\n';
analysis.optimizations.forEach((opt) => {
content += `- ${opt.component}: ${opt.suggestion} (${opt.impact})\n`;
});
content += '\n';
}
// Performance metrics
if (analysis.performance) {
content += '**📊 Performance Metrics:**\n';
content += `- Server Render Time: ${analysis.performance.serverRenderTime}\n`;
content += `- Client Hydration Time: ${analysis.performance.clientHydrationTime || 'N/A'}\n`;
content += `- Total Time: ${analysis.performance.totalTime || 'N/A'}\n\n`;
}
// Recommendations
content += '**💡 Recommendations:**\n';
if (analysis.clientComponents > analysis.serverComponents * 2) {
content += '- Consider converting more components to Server Components\n';
content += '- Move data fetching to Server Components\n';
}
content += '- Keep client components small and focused\n';
content += '- Use Server Components for data-heavy operations\n';
content += '- Minimize props passed from Server to Client Components\n';
return this.createTextResponse(content);
}
catch (error) {
throw new Error(`Failed to analyze server components: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
//# sourceMappingURL=nextjs-performance-handler.js.map