Skip to main content
Glama
build-intelligence-tools.ts16.4 kB
/** * Build Intelligence Tools for WebSee MCP Server * * Provides advanced build artifact analysis capabilities including: * - Webpack/Vite manifest parsing * - Code chunk analysis * - Module dependency tracking * - Bundle size optimization * * @module build-intelligence-tools */ import { z } from 'zod'; import { Page } from 'playwright'; import { BuildArtifactManager } from '../build-artifact-manager.js'; // ============================================================================ // Zod Schemas for Tool Validation // ============================================================================ export const BuildGetManifestSchema = z.object({ url: z.string().url().describe('The application URL to analyze'), }); export const BuildGetChunksSchema = z.object({ url: z.string().url().describe('The application URL to analyze'), }); export const BuildFindModuleSchema = z.object({ url: z.string().url().describe('The application URL to analyze'), moduleName: z.string().describe('Name or path of the module to find'), }); export const BuildGetDependenciesSchema = z.object({ url: z.string().url().describe('The application URL to analyze'), moduleName: z.string().optional().describe('Specific module to get dependencies for (optional)'), }); export const BuildAnalyzeSizeSchema = z.object({ url: z.string().url().describe('The application URL to analyze'), threshold: z .number() .optional() .default(100) .describe('Size threshold in KB to flag large modules'), }); // ============================================================================ // Type Definitions // ============================================================================ export type BuildGetManifestParams = z.infer<typeof BuildGetManifestSchema>; export type BuildGetChunksParams = z.infer<typeof BuildGetChunksSchema>; export type BuildFindModuleParams = z.infer<typeof BuildFindModuleSchema>; export type BuildGetDependenciesParams = z.infer<typeof BuildGetDependenciesSchema>; export type BuildAnalyzeSizeParams = z.infer<typeof BuildAnalyzeSizeSchema>; interface BuildManifestResult { type: string; version: string; chunks: Array<{ id: string | number; files: string[]; size: number; entry?: boolean; initial?: boolean; }>; assets: Array<{ name: string; size: number; chunks: (string | number)[]; }>; modules: Array<{ id: string | number; name: string; size: number; chunks: (string | number)[]; }>; } interface ChunkInfo { id: string | number; files: string[]; modules: string[]; size: number; sizeKB: string; entry?: boolean; initial?: boolean; } interface ModuleInfo { name: string; id: string | number; size: number; sizeKB: string; chunks: (string | number)[]; dependencies: string[]; source?: string; } interface DependencyNode { name: string; version?: string; size: number; sizeKB: string; dependents: string[]; chunks: (string | number)[]; } interface SizeAnalysis { total: number; totalKB: string; totalMB: string; byType: { js: { count: number; size: number; sizeKB: string }; css: { count: number; size: number; sizeKB: string }; other: { count: number; size: number; sizeKB: string }; }; large: Array<{ name: string; size: number; sizeKB: string; type: string; percentage: string; }>; recommendations: string[]; } // ============================================================================ // Helper Functions // ============================================================================ /** * Initialize page and load build artifacts */ async function initializeBuildAnalysis(page: Page, url: string): Promise<BuildArtifactManager> { // Navigate to the URL to trigger build artifact loading await page.goto(url, { waitUntil: 'networkidle' }); // Extract project root from URL or use a default approach // In production, this would be configured or detected const projectRoot = process.env.PROJECT_ROOT || process.cwd(); const buildManager = new BuildArtifactManager(projectRoot); try { await buildManager.loadBuildArtifacts(); } catch (error) { // If local artifacts aren't available, try to fetch from the page await fetchBuildArtifactsFromPage(page, buildManager); } return buildManager; } /** * Attempt to fetch build artifacts from the running page */ async function fetchBuildArtifactsFromPage( page: Page, _buildManager: BuildArtifactManager ): Promise<void> { // Try to extract webpack stats from window.__webpack_require__ or similar const webpackData = await page.evaluate(() => { // Check for webpack if ((window as any).__webpack_require__) { const webpack = (window as any).__webpack_require__; if (webpack.m) { return { type: 'webpack', modules: Object.keys(webpack.m).map(id => ({ id, name: webpack.m[id].toString().substring(0, 100), size: webpack.m[id].toString().length, })), }; } } // Check for vite if ((window as any).__vite__) { return { type: 'vite', modules: [], }; } return null; }); if (!webpackData) { throw new Error('Unable to load build artifacts from filesystem or page runtime'); } } /** * Format bytes to KB string */ function formatKB(bytes: number): string { return (bytes / 1024).toFixed(2); } /** * Format bytes to MB string */ function formatMB(bytes: number): string { return (bytes / 1024 / 1024).toFixed(2); } /** * Get file type from filename */ function getFileType(filename: string): string { const ext = filename.split('.').pop()?.toLowerCase(); if (ext === 'js' || ext === 'mjs' || ext === 'jsx' || ext === 'ts' || ext === 'tsx') { return 'js'; } if (ext === 'css' || ext === 'scss' || ext === 'sass' || ext === 'less') { return 'css'; } return 'other'; } /** * Extract module dependencies from source code */ function extractDependencies(source: string | undefined): string[] { if (!source) return []; const dependencies: string[] = []; // Match ES6 imports const importMatches = source.matchAll(/import\s+.*?\s+from\s+['"]([^'"]+)['"]/g); for (const match of importMatches) { dependencies.push(match[1]); } // Match CommonJS requires const requireMatches = source.matchAll(/require\(['"]([^'"]+)['"]\)/g); for (const match of requireMatches) { dependencies.push(match[1]); } return [...new Set(dependencies)]; } // ============================================================================ // Tool Implementation Functions // ============================================================================ /** * Tool 1: Get build manifest * Returns complete build manifest with chunks, assets, and modules */ export async function buildGetManifest( page: Page, params: BuildGetManifestParams ): Promise<BuildManifestResult> { const buildManager = await initializeBuildAnalysis(page, params.url); if (!buildManager.isLoaded()) { throw new Error('Failed to load build manifest'); } const summary = buildManager.getSummary(); // Get manifest data - this would need to be exposed by BuildArtifactManager // For now, we'll return the summary data return { type: summary.type, version: 'unknown', // BuildArtifactManager doesn't expose version in getSummary chunks: [], // Would need to expose chunks from manifest assets: [], modules: summary.largestModules.map(m => ({ id: m.id, name: m.name, size: m.size, chunks: m.chunks, })), }; } /** * Tool 2: Get all code chunks * Returns detailed information about all chunks in the bundle */ export async function buildGetChunks( page: Page, params: BuildGetChunksParams ): Promise<{ chunks: ChunkInfo[] }> { const buildManager = await initializeBuildAnalysis(page, params.url); if (!buildManager.isLoaded()) { throw new Error('Failed to load build manifest'); } // Extract chunks from page scripts const scriptChunks = await page.$$eval('script[src]', scripts => scripts.map((script: any) => ({ src: script.src, async: script.async, defer: script.defer, })) ); const chunks: ChunkInfo[] = scriptChunks.map((script, index) => { const filename = script.src.split('/').pop() || `chunk-${index}`; return { id: index, files: [filename], modules: [], size: 0, // Size would need to be fetched via network trace sizeKB: '0.00', entry: index === 0, initial: script.defer === false && script.async === false, }; }); return { chunks }; } /** * Tool 3: Find specific module in bundle * Searches for a module by name and returns its details */ export async function buildFindModule( page: Page, params: BuildFindModuleParams ): Promise<ModuleInfo | null> { const buildManager = await initializeBuildAnalysis(page, params.url); if (!buildManager.isLoaded()) { throw new Error('Failed to load build manifest'); } const module = buildManager.findModule(params.moduleName); if (!module) { return null; } const dependencies = extractDependencies(module.source || module.originalSource); return { name: module.name, id: module.id, size: module.size, sizeKB: formatKB(module.size), chunks: module.chunks, dependencies, source: module.source ? module.source.substring(0, 500) + '...' : undefined, }; } /** * Tool 4: Get dependency graph * Returns the dependency graph for all modules or a specific module */ export async function buildGetDependencies( page: Page, params: BuildGetDependenciesParams ): Promise<{ dependencies: DependencyNode[] }> { const buildManager = await initializeBuildAnalysis(page, params.url); if (!buildManager.isLoaded()) { throw new Error('Failed to load build manifest'); } const summary = buildManager.getSummary(); const dependencies: DependencyNode[] = []; // If specific module requested, find its dependencies if (params.moduleName) { const module = buildManager.findModule(params.moduleName); if (module) { const moduleDeps = extractDependencies(module.source || module.originalSource); for (const dep of moduleDeps) { const depModule = buildManager.findModule(dep); dependencies.push({ name: dep, size: depModule?.size || 0, sizeKB: formatKB(depModule?.size || 0), dependents: [params.moduleName], chunks: depModule?.chunks || [], }); } } } else { // Return all modules as dependency nodes for (const module of summary.largestModules) { dependencies.push({ name: module.name, size: module.size, sizeKB: formatKB(module.size), dependents: [], // Would need to compute reverse dependencies chunks: module.chunks, }); } } return { dependencies }; } /** * Tool 5: Analyze bundle sizes * Analyzes bundle sizes and provides optimization recommendations */ export async function buildAnalyzeSize( page: Page, params: BuildAnalyzeSizeParams ): Promise<SizeAnalysis> { const buildManager = await initializeBuildAnalysis(page, params.url); if (!buildManager.isLoaded()) { throw new Error('Failed to load build manifest'); } const summary = buildManager.getSummary(); // Analyze by type const byType = { js: { count: 0, size: 0, sizeKB: '0.00' }, css: { count: 0, size: 0, sizeKB: '0.00' }, other: { count: 0, size: 0, sizeKB: '0.00' }, }; let totalSize = 0; const allAssets: Array<{ name: string; size: number; type: string }> = []; // Add module sizes for (const module of summary.largestModules) { const type = getFileType(module.name); const typeKey = type as keyof typeof byType; byType[typeKey].count++; byType[typeKey].size += module.size; totalSize += module.size; allAssets.push({ name: module.name, size: module.size, type, }); } // Format type sizes for (const type of Object.keys(byType) as Array<keyof typeof byType>) { byType[type].sizeKB = formatKB(byType[type].size); } // Find large assets const thresholdBytes = params.threshold! * 1024; const largeAssets = allAssets .filter(asset => asset.size > thresholdBytes) .sort((a, b) => b.size - a.size) .slice(0, 10) .map(asset => ({ name: asset.name, size: asset.size, sizeKB: formatKB(asset.size), type: asset.type, percentage: ((asset.size / totalSize) * 100).toFixed(2) + '%', })); // Generate recommendations const recommendations: string[] = []; if (largeAssets.length > 0) { recommendations.push( `Found ${largeAssets.length} asset(s) exceeding ${params.threshold}KB threshold. Consider code splitting.` ); } if (byType.js.size > 500 * 1024) { recommendations.push( `Total JavaScript size is ${formatKB(byType.js.size)}KB. Consider lazy loading non-critical modules.` ); } if (summary.totalChunks < 3 && totalSize > 200 * 1024) { recommendations.push( `Only ${summary.totalChunks} chunk(s) detected. Consider implementing code splitting for better caching.` ); } const duplicateModules = summary.largestModules.filter(m => m.chunks.length > 1); if (duplicateModules.length > 5) { recommendations.push( `${duplicateModules.length} modules appear in multiple chunks. Consider using commons chunks or tree shaking.` ); } if (byType.css.size > 100 * 1024) { recommendations.push( `CSS bundle size is ${formatKB(byType.css.size)}KB. Consider critical CSS extraction and lazy loading.` ); } return { total: totalSize, totalKB: formatKB(totalSize), totalMB: formatMB(totalSize), byType, large: largeAssets, recommendations, }; } // ============================================================================ // MCP Tool Definitions Export // ============================================================================ /** * MCP-compatible tool definitions for registration */ export const BUILD_INTELLIGENCE_TOOLS = [ { name: 'build_get_manifest', description: 'Get webpack/vite build manifest including chunks, assets, and modules', inputSchema: { type: 'object' as const, properties: { url: { type: 'string', description: 'The application URL to analyze', }, }, required: ['url'], }, }, { name: 'build_get_chunks', description: 'Get all code chunks with their files, modules, and sizes', inputSchema: { type: 'object' as const, properties: { url: { type: 'string', description: 'The application URL to analyze', }, }, required: ['url'], }, }, { name: 'build_find_module', description: 'Find a specific module in the bundle by name or path', inputSchema: { type: 'object' as const, properties: { url: { type: 'string', description: 'The application URL to analyze', }, moduleName: { type: 'string', description: 'Name or path of the module to find', }, }, required: ['url', 'moduleName'], }, }, { name: 'build_get_dependencies', description: 'Get dependency graph for all modules or a specific module', inputSchema: { type: 'object' as const, properties: { url: { type: 'string', description: 'The application URL to analyze', }, moduleName: { type: 'string', description: 'Specific module to get dependencies for (optional)', }, }, required: ['url'], }, }, { name: 'build_analyze_size', description: 'Analyze bundle sizes by type and identify large modules with optimization recommendations', inputSchema: { type: 'object' as const, properties: { url: { type: 'string', description: 'The application URL to analyze', }, threshold: { type: 'number', description: 'Size threshold in KB to flag large modules (default: 100)', }, }, required: ['url'], }, }, ];

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/1AQuantum/websee-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server