analyze_resources
Audit website resources (images, JS, CSS, fonts) to identify optimization opportunities, improve performance, and streamline content delivery for specified devices.
Instructions
Analyze website resources (images, JS, CSS, fonts) for optimization opportunities
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| device | No | Device to emulate (default: desktop) | desktop |
| minSize | No | Minimum resource size in KB to include | |
| resourceTypes | No | Types of resources to analyze | |
| url | Yes | URL to audit |
Implementation Reference
- src/lighthouse-analysis.ts:64-129 (handler)Core handler function that runs Lighthouse audit, extracts network requests, categorizes resources by type, filters by size/types, computes summary statisticsexport async function analyzeResources( url: string, device: "desktop" | "mobile" = "desktop", resourceTypes?: string[], minSize = DEFAULTS.MIN_RESOURCE_SIZE_KB, ) { const runnerResult = await runRawLighthouseAudit(url, ["performance"], device); const { lhr } = runnerResult; // Get resource summary from network-requests audit const networkAudit = lhr.audits["network-requests"]; if (!networkAudit || !networkAudit.details) { return { url: lhr.finalDisplayedUrl, device, resources: [], summary: {}, fetchTime: lhr.fetchTime, }; } const resources = (networkAudit.details.items || []) .map((item: Record<string, unknown>) => { const sizeKB = ((item.transferSize as number) || 0) / 1024; const resourceType = categorizeResourceType(item); return { url: item.url as string, resourceType, transferSize: (item.transferSize as number) || 0, resourceSize: (item.resourceSize as number) || 0, sizeKB: Math.round(sizeKB * 100) / 100, mimeType: item.mimeType as string, }; }) .filter((resource: { sizeKB: number; resourceType: string }) => { if (minSize && resource.sizeKB < minSize) return false; if (resourceTypes && !resourceTypes.includes(resource.resourceType)) return false; return true; }); // Create summary by resource type const summary = resources.reduce( ( acc: Record<string, { count: number; totalSize: number }>, resource: { resourceType: string; transferSize: number }, ) => { if (!acc[resource.resourceType]) { acc[resource.resourceType] = { count: 0, totalSize: 0 }; } acc[resource.resourceType].count++; acc[resource.resourceType].totalSize += resource.transferSize; return acc; }, {}, ); return { url: lhr.finalDisplayedUrl, device, resources, summary, fetchTime: lhr.fetchTime, }; }
- src/schemas.ts:87-95 (schema)Input schema (Zod) for the analyze_resources tool defining parameters: url, device, resourceTypes, minSizeexport const resourceAnalysisSchema = { url: baseSchemas.url, device: baseSchemas.device, resourceTypes: z .array(z.enum(["images", "javascript", "css", "fonts", "other"])) .optional() .describe("Types of resources to analyze"), minSize: z.number().min(0).optional().describe("Minimum resource size in KB to include"), };
- src/tools/analysis.ts:94-208 (registration)MCP tool registration for 'analyze_resources': sets name, description, schema, and inline handler that calls core analysis and returns formatted content with optimizations/recommendationsserver.tool( "analyze_resources", "Analyze website resources (images, JS, CSS, fonts) for optimization opportunities", resourceAnalysisSchema, async ({ url, device, resourceTypes, minSize }) => { try { const result = await analyzeResources(url, device, resourceTypes, minSize); // Create structured, AI-friendly response const analysisData = { url: result.url, device: result.device, timestamp: result.fetchTime, filters: { resourceTypes: resourceTypes || ["all"], minSizeKB: minSize || 0, }, summary: { totalResources: result.resources.length, totalSizeKB: Math.round((Object.values(result.summary).reduce((sum, data) => sum + data.totalSize, 0) / 1024) * 100) / 100, resourceCounts: Object.fromEntries( Object.entries(result.summary).map(([type, data]) => [ type, { count: data.count, sizeKB: Math.round((data.totalSize / 1024) * 100) / 100, }, ]), ), }, resources: result.resources.slice(0, 50).map((resource) => ({ filename: resource.url.split("/").pop() || resource.url, type: resource.resourceType, sizeKB: Math.round(resource.sizeKB * 100) / 100, mimeType: resource.mimeType || "unknown", url: resource.url, })), optimization: { recommendations: [] as string[], priorities: [] as string[], }, }; // Add type-specific recommendations if (result.summary.images) { analysisData.optimization.recommendations.push("Convert images to WebP/AVIF formats"); analysisData.optimization.recommendations.push("Implement lazy loading for images"); analysisData.optimization.priorities.push("images"); } if (result.summary.javascript) { analysisData.optimization.recommendations.push("Minify and compress JavaScript files"); analysisData.optimization.recommendations.push("Remove unused JavaScript code"); analysisData.optimization.priorities.push("javascript"); } if (result.summary.css) { analysisData.optimization.recommendations.push("Minify CSS and remove unused styles"); analysisData.optimization.priorities.push("css"); } if (result.summary.fonts) { analysisData.optimization.recommendations.push("Use font-display: swap for better loading"); analysisData.optimization.priorities.push("fonts"); } if (analysisData.optimization.recommendations.length === 0) { analysisData.optimization.recommendations.push("Resource usage appears optimized"); } // Add truncation info if needed if (result.resources.length > 50) { analysisData.resources.push({ filename: `... and ${result.resources.length - 50} more resources`, type: "truncated", sizeKB: 0, mimeType: "info", url: "", }); } const summary = `Analyzed ${result.resources.length} resources (${analysisData.summary.totalSizeKB}KB total) - ${analysisData.optimization.recommendations.length} optimization opportunities found`; return { content: createStructuredAnalysis("Resource Analysis", analysisData, summary), }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: "text" as const, text: `Analysis failed: ${errorMessage}`, }, { type: "text" as const, text: JSON.stringify( { error: true, url, device, resourceTypes, minSize, message: errorMessage, timestamp: new Date().toISOString(), }, null, 2, ), }, ], isError: true, }; } }, );
- src/lighthouse-analysis.ts:47-61 (helper)Helper function to categorize Lighthouse network resources into types: images, javascript, css, fonts, other based on resourceType or mimeTypefunction categorizeResourceType(item: Record<string, unknown>): string { if (item.resourceType) { return (item.resourceType as string).toLowerCase(); } if (item.mimeType) { const mimeType = item.mimeType as string; if (mimeType.startsWith("image/")) return "images"; if (mimeType.includes("javascript")) return "javascript"; if (mimeType.includes("css")) return "css"; if (mimeType.includes("font")) return "fonts"; } return "other"; }