@perfonext/build-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +86 -0
  3. package/dist/format.d.ts +4 -0
  4. package/dist/format.d.ts.map +1 -0
  5. package/dist/format.js +19 -0
  6. package/dist/format.js.map +1 -0
  7. package/dist/index.d.ts +3 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +18 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/parser/analysis.d.ts +15 -0
  12. package/dist/parser/analysis.d.ts.map +1 -0
  13. package/dist/parser/analysis.js +110 -0
  14. package/dist/parser/analysis.js.map +1 -0
  15. package/dist/parser/build-stats.d.ts +4 -0
  16. package/dist/parser/build-stats.d.ts.map +1 -0
  17. package/dist/parser/build-stats.js +201 -0
  18. package/dist/parser/build-stats.js.map +1 -0
  19. package/dist/parser/types.d.ts +86 -0
  20. package/dist/parser/types.d.ts.map +1 -0
  21. package/dist/parser/types.js +2 -0
  22. package/dist/parser/types.js.map +1 -0
  23. package/dist/store.d.ts +12 -0
  24. package/dist/store.d.ts.map +1 -0
  25. package/dist/store.js +18 -0
  26. package/dist/store.js.map +1 -0
  27. package/dist/tools/compare-builds.d.ts +3 -0
  28. package/dist/tools/compare-builds.d.ts.map +1 -0
  29. package/dist/tools/compare-builds.js +86 -0
  30. package/dist/tools/compare-builds.js.map +1 -0
  31. package/dist/tools/get-largest-routes.d.ts +3 -0
  32. package/dist/tools/get-largest-routes.d.ts.map +1 -0
  33. package/dist/tools/get-largest-routes.js +51 -0
  34. package/dist/tools/get-largest-routes.js.map +1 -0
  35. package/dist/tools/get-shared-chunks.d.ts +3 -0
  36. package/dist/tools/get-shared-chunks.d.ts.map +1 -0
  37. package/dist/tools/get-shared-chunks.js +48 -0
  38. package/dist/tools/get-shared-chunks.js.map +1 -0
  39. package/dist/tools/load-build-stats.d.ts +3 -0
  40. package/dist/tools/load-build-stats.d.ts.map +1 -0
  41. package/dist/tools/load-build-stats.js +31 -0
  42. package/dist/tools/load-build-stats.js.map +1 -0
  43. package/package.json +56 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Perfonext
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # perfonext-build-mcp
2
+
3
+ [![npm](https://img.shields.io/npm/v/@perfonext/build-mcp)](https://www.npmjs.com/package/@perfonext/build-mcp)
4
+
5
+ `perfonext-build-mcp` is an MCP server for loading and analyzing Next.js build artifacts. It gives GitHub Copilot and other MCP clients structured bundle and route-size data they can reason over instead of forcing the model to inspect raw `.next` manifests.
6
+
7
+ ## What It Does
8
+
9
+ - loads Next.js build artifacts from a `.next` directory
10
+ - ranks the largest user-facing routes by emitted bundle footprint
11
+ - identifies the heaviest shared chunks that affect multiple routes
12
+ - keeps loaded build snapshots in memory so an MCP client can inspect them without re-reading the same build
13
+
14
+ ## Inputs
15
+
16
+ The MVP reads build artifacts developers already have after running `next build`:
17
+
18
+ - `.next/build-manifest.json`
19
+ - `.next/prerender-manifest.json` when present
20
+ - `.next/app-build-manifest.json` when present
21
+ - optional captured `next build` output text to derive build duration
22
+
23
+ ## Tools
24
+
25
+ | Tool | Description |
26
+ |------|-------------|
27
+ | `load_build_stats` | Parse a Next.js `.next` directory and load the build snapshot into memory |
28
+ | `get_largest_routes` | Rank the heaviest user-facing routes by total emitted chunk bytes |
29
+ | `get_shared_chunks` | Rank shared chunks by size and show which routes depend on them |
30
+ | `compare_builds` | Compare a baseline and current build snapshot to show which routes and chunks grew or shrank |
31
+
32
+ The output stays machine-readable and includes raw byte counts so Copilot can explain regressions, prioritise fixes, and suggest concrete dependency or import-level follow-up.
33
+
34
+ ## Install
35
+
36
+ Run directly with `npx`:
37
+
38
+ ```bash
39
+ npx -y @perfonext/build-mcp
40
+ ```
41
+
42
+ Or install globally:
43
+
44
+ ```bash
45
+ npm install -g @perfonext/build-mcp
46
+ ```
47
+
48
+ The executable command remains `perfonext-build-mcp` after installation.
49
+
50
+ ## MCP Configuration
51
+
52
+ Add this server to VS Code settings:
53
+
54
+ ```json
55
+ {
56
+ "mcp": {
57
+ "servers": {
58
+ "perfonext-build": {
59
+ "command": "npx",
60
+ "args": ["-y", "@perfonext/build-mcp"]
61
+ }
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ ## Example Copilot Prompts
68
+
69
+ - "Load the Next.js build in `./.next` and show me the largest routes."
70
+ - "Which shared chunks are affecting the most routes in this build?"
71
+ - "Summarize the build footprint and tell me which routes ship the most JavaScript."
72
+ - "Compare my baseline and current `.next` builds and show me which routes or shared chunks grew the most."
73
+
74
+ ## Development
75
+
76
+ ```bash
77
+ npm install
78
+ npm run build
79
+ npm test
80
+ ```
81
+
82
+ Sample fixtures for local validation live under `tests/fixtures/`.
83
+
84
+ ## License
85
+
86
+ MIT
@@ -0,0 +1,4 @@
1
+ export declare function formatBytes(bytes: number): string;
2
+ export declare function formatMs(value: number | null): string | null;
3
+ export declare function formatPct(value: number): string;
4
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAUjD;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAM5D;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C"}
package/dist/format.js ADDED
@@ -0,0 +1,19 @@
1
+ export function formatBytes(bytes) {
2
+ if (bytes < 1024) {
3
+ return `${bytes} B`;
4
+ }
5
+ if (bytes < 1024 * 1024) {
6
+ return `${(bytes / 1024).toFixed(1)} KB`;
7
+ }
8
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
9
+ }
10
+ export function formatMs(value) {
11
+ if (value === null) {
12
+ return null;
13
+ }
14
+ return `${value.toFixed(2)}ms`;
15
+ }
16
+ export function formatPct(value) {
17
+ return `${(value * 100).toFixed(1)}%`;
18
+ }
19
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,IAAI,KAAK,GAAG,IAAI,EAAE,CAAC;QACjB,OAAO,GAAG,KAAK,IAAI,CAAC;IACtB,CAAC;IAED,IAAI,KAAK,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;QACxB,OAAO,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;IAC3C,CAAC;IAED,OAAO,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,KAAoB;IAC3C,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,OAAO,GAAG,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;AACxC,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { registerLoadBuildStats } from './tools/load-build-stats.js';
5
+ import { registerGetLargestRoutes } from './tools/get-largest-routes.js';
6
+ import { registerGetSharedChunks } from './tools/get-shared-chunks.js';
7
+ import { registerCompareBuilds } from './tools/compare-builds.js';
8
+ const server = new McpServer({
9
+ name: 'perfonext-build-mcp',
10
+ version: '0.1.0',
11
+ });
12
+ registerLoadBuildStats(server);
13
+ registerGetLargestRoutes(server);
14
+ registerGetSharedChunks(server);
15
+ registerCompareBuilds(server);
16
+ const transport = new StdioServerTransport();
17
+ await server.connect(transport);
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAElE,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,qBAAqB;IAC3B,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,sBAAsB,CAAC,MAAM,CAAC,CAAC;AAC/B,wBAAwB,CAAC,MAAM,CAAC,CAAC;AACjC,uBAAuB,CAAC,MAAM,CAAC,CAAC;AAChC,qBAAqB,CAAC,MAAM,CAAC,CAAC;AAE9B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { BuildComparison, ParsedBuildStats, RouteSummaryEntry, SharedChunkSummaryEntry } from './types.js';
2
+ export declare function getBuildSummary(build: ParsedBuildStats): {
3
+ buildId: string;
4
+ buildDir: string;
5
+ routeCount: number;
6
+ chunkCount: number;
7
+ sharedChunkCount: number;
8
+ totalChunkBytes: number;
9
+ sharedChunkBytes: number;
10
+ buildTimeMs: number | null;
11
+ };
12
+ export declare function getLargestRoutes(build: ParsedBuildStats, limit: number): RouteSummaryEntry[];
13
+ export declare function getSharedChunks(build: ParsedBuildStats, limit: number): SharedChunkSummaryEntry[];
14
+ export declare function compareBuilds(baseline: ParsedBuildStats, current: ParsedBuildStats, limit: number): BuildComparison;
15
+ //# sourceMappingURL=analysis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analysis.d.ts","sourceRoot":"","sources":["../../src/parser/analysis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EAEf,gBAAgB,EAEhB,iBAAiB,EACjB,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAUpB,wBAAgB,eAAe,CAAC,KAAK,EAAE,gBAAgB,GAAG;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAWA;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAc5F;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,GAAG,uBAAuB,EAAE,CAWjG;AAED,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,gBAAgB,EACzB,KAAK,EAAE,MAAM,GACZ,eAAe,CAqEjB"}
@@ -0,0 +1,110 @@
1
+ function calculateDeltaRatio(baseline, current) {
2
+ if (baseline === 0) {
3
+ return current === 0 ? 0 : null;
4
+ }
5
+ return (current - baseline) / baseline;
6
+ }
7
+ export function getBuildSummary(build) {
8
+ return {
9
+ buildId: build.id,
10
+ buildDir: build.buildDir,
11
+ routeCount: build.routes.length,
12
+ chunkCount: build.chunks.length,
13
+ sharedChunkCount: build.chunks.filter(chunk => chunk.isShared).length,
14
+ totalChunkBytes: build.totalChunkBytes,
15
+ sharedChunkBytes: build.sharedChunkBytes,
16
+ buildTimeMs: build.buildTimeMs,
17
+ };
18
+ }
19
+ export function getLargestRoutes(build, limit) {
20
+ return build.routes.slice(0, limit).map(route => ({
21
+ path: route.path,
22
+ type: route.type,
23
+ prerenderBlockedReason: route.prerenderBlockedReason,
24
+ totalBytes: route.totalBytes,
25
+ initialLoadBytes: route.initialLoadBytes,
26
+ sharedChunkBytes: route.sharedChunkBytes,
27
+ exclusiveChunkBytes: route.exclusiveChunkBytes,
28
+ sharedRatio: route.totalBytes === 0 ? 0 : route.sharedChunkBytes / route.totalBytes,
29
+ chunkCount: route.chunkPaths.length,
30
+ isPrerendered: route.isPrerendered,
31
+ isAppRoute: route.isAppRoute,
32
+ }));
33
+ }
34
+ export function getSharedChunks(build, limit) {
35
+ return build.chunks
36
+ .filter(chunk => chunk.isShared)
37
+ .slice(0, limit)
38
+ .map(chunk => ({
39
+ chunkPath: chunk.chunkPath,
40
+ sizeBytes: chunk.sizeBytes,
41
+ routeCount: chunk.routeCount,
42
+ sharedByRoutes: chunk.sharedByRoutes,
43
+ shareOfAllChunkBytes: build.totalChunkBytes === 0 ? 0 : chunk.sizeBytes / build.totalChunkBytes,
44
+ }));
45
+ }
46
+ export function compareBuilds(baseline, current, limit) {
47
+ const baselineRoutes = new Map(baseline.routes.map(route => [route.path, route]));
48
+ const currentRoutes = new Map(current.routes.map(route => [route.path, route]));
49
+ const allRoutePaths = Array.from(new Set([...baselineRoutes.keys(), ...currentRoutes.keys()]));
50
+ const routeDeltas = allRoutePaths
51
+ .map(path => {
52
+ const baselineRoute = baselineRoutes.get(path);
53
+ const currentRoute = currentRoutes.get(path);
54
+ const baselineBytes = baselineRoute?.totalBytes ?? 0;
55
+ const currentBytes = currentRoute?.totalBytes ?? 0;
56
+ return {
57
+ path,
58
+ baselineBytes,
59
+ currentBytes,
60
+ deltaBytes: currentBytes - baselineBytes,
61
+ deltaRatio: calculateDeltaRatio(baselineBytes, currentBytes),
62
+ };
63
+ })
64
+ .sort((left, right) => Math.abs(right.deltaBytes) - Math.abs(left.deltaBytes))
65
+ .slice(0, limit);
66
+ const baselineChunks = new Map(baseline.chunks.map(chunk => [chunk.chunkPath, chunk]));
67
+ const currentChunks = new Map(current.chunks.map(chunk => [chunk.chunkPath, chunk]));
68
+ const allChunkPaths = Array.from(new Set([...baselineChunks.keys(), ...currentChunks.keys()]));
69
+ const chunkDeltas = allChunkPaths
70
+ .map(chunkPath => {
71
+ const baselineChunk = baselineChunks.get(chunkPath);
72
+ const currentChunk = currentChunks.get(chunkPath);
73
+ const baselineBytes = baselineChunk?.sizeBytes ?? 0;
74
+ const currentBytes = currentChunk?.sizeBytes ?? 0;
75
+ return {
76
+ chunkPath,
77
+ baselineBytes,
78
+ currentBytes,
79
+ deltaBytes: currentBytes - baselineBytes,
80
+ deltaRatio: calculateDeltaRatio(baselineBytes, currentBytes),
81
+ baselineRouteCount: baselineChunk?.routeCount ?? 0,
82
+ currentRouteCount: currentChunk?.routeCount ?? 0,
83
+ };
84
+ })
85
+ .sort((left, right) => Math.abs(right.deltaBytes) - Math.abs(left.deltaBytes))
86
+ .slice(0, limit);
87
+ return {
88
+ baselineBuildId: baseline.id,
89
+ currentBuildId: current.id,
90
+ baselineTotalChunkBytes: baseline.totalChunkBytes,
91
+ currentTotalChunkBytes: current.totalChunkBytes,
92
+ totalChunkDeltaBytes: current.totalChunkBytes - baseline.totalChunkBytes,
93
+ totalChunkDeltaRatio: calculateDeltaRatio(baseline.totalChunkBytes, current.totalChunkBytes),
94
+ baselineSharedChunkBytes: baseline.sharedChunkBytes,
95
+ currentSharedChunkBytes: current.sharedChunkBytes,
96
+ sharedChunkDeltaBytes: current.sharedChunkBytes - baseline.sharedChunkBytes,
97
+ sharedChunkDeltaRatio: calculateDeltaRatio(baseline.sharedChunkBytes, current.sharedChunkBytes),
98
+ baselineBuildTimeMs: baseline.buildTimeMs,
99
+ currentBuildTimeMs: current.buildTimeMs,
100
+ buildTimeDeltaMs: baseline.buildTimeMs === null || current.buildTimeMs === null
101
+ ? null
102
+ : current.buildTimeMs - baseline.buildTimeMs,
103
+ buildTimeDeltaRatio: baseline.buildTimeMs === null || current.buildTimeMs === null
104
+ ? null
105
+ : calculateDeltaRatio(baseline.buildTimeMs, current.buildTimeMs),
106
+ routeDeltas,
107
+ chunkDeltas,
108
+ };
109
+ }
110
+ //# sourceMappingURL=analysis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analysis.js","sourceRoot":"","sources":["../../src/parser/analysis.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { ParsedBuildStats } from './types.js';
2
+ export declare function parseBuildDurationMs(output: string): number | null;
3
+ export declare function parseBuildStats(buildDirPath: string, buildOutputPath?: string): Promise<ParsedBuildStats>;
4
+ //# sourceMappingURL=build-stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-stats.d.ts","sourceRoot":"","sources":["../../src/parser/build-stats.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAA0B,gBAAgB,EAAqC,MAAM,YAAY,CAAC;AAmH9G,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAqBlE;AAED,wBAAsB,eAAe,CACnC,YAAY,EAAE,MAAM,EACpB,eAAe,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,gBAAgB,CAAC,CA+H3B"}
@@ -0,0 +1,201 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { readFile, stat } from 'node:fs/promises';
3
+ import { join, resolve } from 'node:path';
4
+ function isRecord(value) {
5
+ return value !== null && typeof value === 'object';
6
+ }
7
+ async function readJsonIfPresent(filePath) {
8
+ try {
9
+ const content = await readFile(filePath, 'utf-8');
10
+ return JSON.parse(content);
11
+ }
12
+ catch (error) {
13
+ if (error.code === 'ENOENT') {
14
+ return null;
15
+ }
16
+ throw error;
17
+ }
18
+ }
19
+ async function getFileSizeBytes(filePath) {
20
+ try {
21
+ const result = await stat(filePath);
22
+ return result.size;
23
+ }
24
+ catch (error) {
25
+ if (error.code === 'ENOENT') {
26
+ return 0;
27
+ }
28
+ throw error;
29
+ }
30
+ }
31
+ function parseRouteMap(raw) {
32
+ if (!isRecord(raw)) {
33
+ return {};
34
+ }
35
+ const routeMap = {};
36
+ for (const [route, files] of Object.entries(raw)) {
37
+ if (!Array.isArray(files)) {
38
+ continue;
39
+ }
40
+ routeMap[route] = files.filter((file) => typeof file === 'string');
41
+ }
42
+ return routeMap;
43
+ }
44
+ function shouldIncludeRoute(route) {
45
+ return !route.startsWith('/_');
46
+ }
47
+ function getRouteType(route, prerenderRoutes, dynamicRoutes) {
48
+ if (route in dynamicRoutes || route.includes('[')) {
49
+ return 'dynamic';
50
+ }
51
+ const prerenderRoute = prerenderRoutes[route];
52
+ if (prerenderRoute &&
53
+ typeof prerenderRoute.initialRevalidateSeconds === 'number' &&
54
+ Number.isFinite(prerenderRoute.initialRevalidateSeconds) &&
55
+ prerenderRoute.initialRevalidateSeconds > 0) {
56
+ return 'isr';
57
+ }
58
+ return 'static';
59
+ }
60
+ function getPrerenderBlockedReason(route, prerenderRoutes, dynamicRoutes) {
61
+ if (route in prerenderRoutes) {
62
+ const revalidate = prerenderRoutes[route].initialRevalidateSeconds;
63
+ if (typeof revalidate === 'number' && Number.isFinite(revalidate) && revalidate > 0) {
64
+ return 'isr';
65
+ }
66
+ return null;
67
+ }
68
+ // In dynamicRoutes: has getStaticPaths but some or all paths render on-demand
69
+ if (route in dynamicRoutes) {
70
+ return 'dynamic-params';
71
+ }
72
+ // Not prerendered at all
73
+ if (route.includes('[')) {
74
+ return 'dynamic-params';
75
+ }
76
+ return 'server-side-props';
77
+ }
78
+ export function parseBuildDurationMs(output) {
79
+ const match = output.match(/(?:compiled(?:\s+\w+)*\s+in|done\s+in)\s+(\d+(?:\.\d+)?)\s*(ms|s|m)\b/i);
80
+ if (!match) {
81
+ return null;
82
+ }
83
+ const value = Number(match[1]);
84
+ const unit = match[2].toLowerCase();
85
+ if (!Number.isFinite(value)) {
86
+ return null;
87
+ }
88
+ if (unit === 'ms') {
89
+ return Math.round(value);
90
+ }
91
+ if (unit === 's') {
92
+ return Math.round(value * 1000);
93
+ }
94
+ return Math.round(value * 60 * 1000);
95
+ }
96
+ export async function parseBuildStats(buildDirPath, buildOutputPath) {
97
+ const buildDir = resolve(buildDirPath);
98
+ const buildManifestPath = join(buildDir, 'build-manifest.json');
99
+ const prerenderManifestPath = join(buildDir, 'prerender-manifest.json');
100
+ const appBuildManifestPath = join(buildDir, 'app-build-manifest.json');
101
+ const buildManifest = await readJsonIfPresent(buildManifestPath);
102
+ if (!buildManifest) {
103
+ throw new Error(`Could not find build-manifest.json in ${buildDir}. Run next build first and point this tool at the .next directory.`);
104
+ }
105
+ const prerenderManifest = await readJsonIfPresent(prerenderManifestPath);
106
+ const appBuildManifest = await readJsonIfPresent(appBuildManifestPath);
107
+ const pagesRouteMap = parseRouteMap(buildManifest.pages);
108
+ const appRouteMap = parseRouteMap(appBuildManifest?.pages);
109
+ const prerenderRoutes = isRecord(prerenderManifest?.routes)
110
+ ? prerenderManifest.routes
111
+ : {};
112
+ const dynamicRoutes = isRecord(prerenderManifest?.dynamicRoutes)
113
+ ? prerenderManifest.dynamicRoutes
114
+ : {};
115
+ const allRoutes = new Map();
116
+ for (const [route, chunkPaths] of Object.entries(pagesRouteMap)) {
117
+ if (!shouldIncludeRoute(route)) {
118
+ continue;
119
+ }
120
+ allRoutes.set(route, {
121
+ chunkPaths: Array.from(new Set(chunkPaths)),
122
+ isAppRoute: false,
123
+ });
124
+ }
125
+ for (const [route, chunkPaths] of Object.entries(appRouteMap)) {
126
+ if (!shouldIncludeRoute(route)) {
127
+ continue;
128
+ }
129
+ const existing = allRoutes.get(route);
130
+ allRoutes.set(route, {
131
+ chunkPaths: Array.from(new Set([...(existing?.chunkPaths ?? []), ...chunkPaths])),
132
+ isAppRoute: true,
133
+ });
134
+ }
135
+ const chunkRouteMap = new Map();
136
+ for (const [route, details] of allRoutes) {
137
+ for (const chunkPath of details.chunkPaths) {
138
+ if (!chunkRouteMap.has(chunkPath)) {
139
+ chunkRouteMap.set(chunkPath, new Set());
140
+ }
141
+ chunkRouteMap.get(chunkPath).add(route);
142
+ }
143
+ }
144
+ const chunkSizeEntries = await Promise.all(Array.from(chunkRouteMap.keys()).map(async (chunkPath) => {
145
+ const normalized = chunkPath.replace(/^\//, '');
146
+ const sizeBytes = await getFileSizeBytes(join(buildDir, normalized));
147
+ return [chunkPath, sizeBytes];
148
+ }));
149
+ const chunkSizeMap = new Map(chunkSizeEntries);
150
+ const chunks = Array.from(chunkRouteMap.entries())
151
+ .map(([chunkPath, routeSet]) => ({
152
+ chunkPath,
153
+ sizeBytes: chunkSizeMap.get(chunkPath) ?? 0,
154
+ routeCount: routeSet.size,
155
+ sharedByRoutes: Array.from(routeSet).sort(),
156
+ isShared: routeSet.size > 1,
157
+ }))
158
+ .sort((left, right) => right.sizeBytes - left.sizeBytes);
159
+ const routes = Array.from(allRoutes.entries())
160
+ .map(([route, details]) => {
161
+ const totalBytes = details.chunkPaths.reduce((sum, chunkPath) => sum + (chunkSizeMap.get(chunkPath) ?? 0), 0);
162
+ const sharedChunkBytes = details.chunkPaths.reduce((sum, chunkPath) => {
163
+ const routeCount = chunkRouteMap.get(chunkPath)?.size ?? 0;
164
+ if (routeCount > 1) {
165
+ return sum + (chunkSizeMap.get(chunkPath) ?? 0);
166
+ }
167
+ return sum;
168
+ }, 0);
169
+ return {
170
+ path: route,
171
+ type: getRouteType(route, prerenderRoutes, dynamicRoutes),
172
+ prerenderBlockedReason: getPrerenderBlockedReason(route, prerenderRoutes, dynamicRoutes),
173
+ chunkPaths: details.chunkPaths,
174
+ totalBytes,
175
+ initialLoadBytes: totalBytes,
176
+ sharedChunkBytes,
177
+ exclusiveChunkBytes: totalBytes - sharedChunkBytes,
178
+ isPrerendered: route in prerenderRoutes,
179
+ isAppRoute: details.isAppRoute,
180
+ };
181
+ })
182
+ .sort((left, right) => right.totalBytes - left.totalBytes);
183
+ let buildTimeMs = null;
184
+ let resolvedBuildOutputPath = null;
185
+ if (buildOutputPath) {
186
+ resolvedBuildOutputPath = resolve(buildOutputPath);
187
+ const output = await readFile(resolvedBuildOutputPath, 'utf-8');
188
+ buildTimeMs = parseBuildDurationMs(output);
189
+ }
190
+ return {
191
+ id: randomUUID(),
192
+ buildDir,
193
+ buildOutputPath: resolvedBuildOutputPath,
194
+ routes,
195
+ chunks,
196
+ totalChunkBytes: chunks.reduce((sum, chunk) => sum + chunk.sizeBytes, 0),
197
+ sharedChunkBytes: chunks.reduce((sum, chunk) => sum + (chunk.isShared ? chunk.sizeBytes : 0), 0),
198
+ buildTimeMs,
199
+ };
200
+ }
201
+ //# sourceMappingURL=build-stats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-stats.js","sourceRoot":"","sources":["../../src/parser/build-stats.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ export type RouteType = 'static' | 'dynamic' | 'isr';
2
+ export type PrerenderBlockedReason = 'isr' | 'dynamic-params' | 'server-side-props' | null;
3
+ export interface BuildRoute {
4
+ path: string;
5
+ type: RouteType;
6
+ prerenderBlockedReason: PrerenderBlockedReason;
7
+ chunkPaths: string[];
8
+ totalBytes: number;
9
+ initialLoadBytes: number;
10
+ sharedChunkBytes: number;
11
+ exclusiveChunkBytes: number;
12
+ isPrerendered: boolean;
13
+ isAppRoute: boolean;
14
+ }
15
+ export interface BuildChunk {
16
+ chunkPath: string;
17
+ sizeBytes: number;
18
+ routeCount: number;
19
+ sharedByRoutes: string[];
20
+ isShared: boolean;
21
+ }
22
+ export interface ParsedBuildStats {
23
+ id: string;
24
+ buildDir: string;
25
+ buildOutputPath: string | null;
26
+ routes: BuildRoute[];
27
+ chunks: BuildChunk[];
28
+ totalChunkBytes: number;
29
+ sharedChunkBytes: number;
30
+ buildTimeMs: number | null;
31
+ }
32
+ export interface RouteSummaryEntry {
33
+ path: string;
34
+ type: RouteType;
35
+ prerenderBlockedReason: PrerenderBlockedReason;
36
+ totalBytes: number;
37
+ initialLoadBytes: number;
38
+ sharedChunkBytes: number;
39
+ exclusiveChunkBytes: number;
40
+ sharedRatio: number;
41
+ chunkCount: number;
42
+ isPrerendered: boolean;
43
+ isAppRoute: boolean;
44
+ }
45
+ export interface SharedChunkSummaryEntry {
46
+ chunkPath: string;
47
+ sizeBytes: number;
48
+ routeCount: number;
49
+ sharedByRoutes: string[];
50
+ shareOfAllChunkBytes: number;
51
+ }
52
+ export interface RouteDeltaEntry {
53
+ path: string;
54
+ baselineBytes: number;
55
+ currentBytes: number;
56
+ deltaBytes: number;
57
+ deltaRatio: number | null;
58
+ }
59
+ export interface ChunkDeltaEntry {
60
+ chunkPath: string;
61
+ baselineBytes: number;
62
+ currentBytes: number;
63
+ deltaBytes: number;
64
+ deltaRatio: number | null;
65
+ baselineRouteCount: number;
66
+ currentRouteCount: number;
67
+ }
68
+ export interface BuildComparison {
69
+ baselineBuildId: string;
70
+ currentBuildId: string;
71
+ baselineTotalChunkBytes: number;
72
+ currentTotalChunkBytes: number;
73
+ totalChunkDeltaBytes: number;
74
+ totalChunkDeltaRatio: number | null;
75
+ baselineSharedChunkBytes: number;
76
+ currentSharedChunkBytes: number;
77
+ sharedChunkDeltaBytes: number;
78
+ sharedChunkDeltaRatio: number | null;
79
+ baselineBuildTimeMs: number | null;
80
+ currentBuildTimeMs: number | null;
81
+ buildTimeDeltaMs: number | null;
82
+ buildTimeDeltaRatio: number | null;
83
+ routeDeltas: RouteDeltaEntry[];
84
+ chunkDeltas: ChunkDeltaEntry[];
85
+ }
86
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/parser/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/parser/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,12 @@
1
+ import type { ParsedBuildStats } from './parser/types.js';
2
+ export declare function storeBuildStats(build: ParsedBuildStats): void;
3
+ export declare function getBuildStats(id: string): ParsedBuildStats | undefined;
4
+ export declare function listBuildStats(): Array<{
5
+ id: string;
6
+ buildDir: string;
7
+ routeCount: number;
8
+ chunkCount: number;
9
+ totalChunkBytes: number;
10
+ buildTimeMs: number | null;
11
+ }>;
12
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAI1D,wBAAgB,eAAe,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAE7D;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAEtE;AAED,wBAAgB,cAAc,IAAI,KAAK,CAAC;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC,CASD"}
package/dist/store.js ADDED
@@ -0,0 +1,18 @@
1
+ const builds = new Map();
2
+ export function storeBuildStats(build) {
3
+ builds.set(build.id, build);
4
+ }
5
+ export function getBuildStats(id) {
6
+ return builds.get(id);
7
+ }
8
+ export function listBuildStats() {
9
+ return Array.from(builds.values()).map(build => ({
10
+ id: build.id,
11
+ buildDir: build.buildDir,
12
+ routeCount: build.routes.length,
13
+ chunkCount: build.chunks.length,
14
+ totalChunkBytes: build.totalChunkBytes,
15
+ buildTimeMs: build.buildTimeMs,
16
+ }));
17
+ }
18
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,GAAG,IAAI,GAAG,EAA4B,CAAC;AAEnD,MAAM,UAAU,eAAe,CAAC,KAAuB;IACrD,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,EAAU;IACtC,OAAO,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,cAAc;IAQ5B,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC/C,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM;QAC/B,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM;QAC/B,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,WAAW,EAAE,KAAK,CAAC,WAAW;KAC/B,CAAC,CAAC,CAAC;AACN,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerCompareBuilds(server: McpServer): void;
3
+ //# sourceMappingURL=compare-builds.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare-builds.d.ts","sourceRoot":"","sources":["../../src/tools/compare-builds.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAWpE,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAkF7D"}
@@ -0,0 +1,86 @@
1
+ import { z } from 'zod';
2
+ import { formatBytes, formatMs, formatPct } from '../format.js';
3
+ import { compareBuilds as compareLoadedBuilds } from '../parser/analysis.js';
4
+ import { getBuildStats, listBuildStats } from '../store.js';
5
+ function formatNullableRatio(value) {
6
+ return value === null ? null : formatPct(value);
7
+ }
8
+ export function registerCompareBuilds(server) {
9
+ server.registerTool('compare_builds', {
10
+ title: 'Compare Builds',
11
+ description: 'Compare two loaded Next.js builds and show which routes and chunks grew or shrank the most.',
12
+ inputSchema: {
13
+ baselineBuildId: z.string().optional().describe('Baseline build ID from load_build_stats. Omit to list loaded builds.'),
14
+ currentBuildId: z.string().optional().describe('Current build ID from load_build_stats. Omit to list loaded builds.'),
15
+ limit: z.number().int().positive().max(25).optional().describe('How many route and chunk deltas to include. Defaults to 10.'),
16
+ },
17
+ }, async ({ baselineBuildId, currentBuildId, limit }) => {
18
+ if (!baselineBuildId || !currentBuildId) {
19
+ return {
20
+ content: [{
21
+ type: 'text',
22
+ text: JSON.stringify({
23
+ builds: listBuildStats().map(build => ({
24
+ id: build.id,
25
+ buildDir: build.buildDir,
26
+ routeCount: build.routeCount,
27
+ chunkCount: build.chunkCount,
28
+ totalChunkBytes: build.totalChunkBytes,
29
+ totalChunkBytesText: formatBytes(build.totalChunkBytes),
30
+ buildTimeText: formatMs(build.buildTimeMs),
31
+ })),
32
+ }, null, 2),
33
+ }],
34
+ };
35
+ }
36
+ const baseline = getBuildStats(baselineBuildId);
37
+ if (!baseline) {
38
+ throw new Error(`Build "${baselineBuildId}" not found. Call compare_builds without IDs to list loaded builds.`);
39
+ }
40
+ const current = getBuildStats(currentBuildId);
41
+ if (!current) {
42
+ throw new Error(`Build "${currentBuildId}" not found. Call compare_builds without IDs to list loaded builds.`);
43
+ }
44
+ const comparison = compareLoadedBuilds(baseline, current, limit ?? 10);
45
+ return {
46
+ content: [{
47
+ type: 'text',
48
+ text: JSON.stringify({
49
+ ...comparison,
50
+ baselineTotalChunkBytesText: formatBytes(comparison.baselineTotalChunkBytes),
51
+ currentTotalChunkBytesText: formatBytes(comparison.currentTotalChunkBytes),
52
+ totalChunkDeltaBytesText: formatBytes(Math.abs(comparison.totalChunkDeltaBytes)),
53
+ totalChunkDeltaDirection: comparison.totalChunkDeltaBytes >= 0 ? 'growth' : 'shrink',
54
+ totalChunkDeltaRatioText: formatNullableRatio(comparison.totalChunkDeltaRatio),
55
+ baselineSharedChunkBytesText: formatBytes(comparison.baselineSharedChunkBytes),
56
+ currentSharedChunkBytesText: formatBytes(comparison.currentSharedChunkBytes),
57
+ sharedChunkDeltaBytesText: formatBytes(Math.abs(comparison.sharedChunkDeltaBytes)),
58
+ sharedChunkDeltaDirection: comparison.sharedChunkDeltaBytes >= 0 ? 'growth' : 'shrink',
59
+ sharedChunkDeltaRatioText: formatNullableRatio(comparison.sharedChunkDeltaRatio),
60
+ baselineBuildTimeText: formatMs(comparison.baselineBuildTimeMs),
61
+ currentBuildTimeText: formatMs(comparison.currentBuildTimeMs),
62
+ buildTimeDeltaText: comparison.buildTimeDeltaMs === null ? null : formatMs(Math.abs(comparison.buildTimeDeltaMs)),
63
+ buildTimeDeltaDirection: comparison.buildTimeDeltaMs === null ? null : comparison.buildTimeDeltaMs >= 0 ? 'slower' : 'faster',
64
+ buildTimeDeltaRatioText: formatNullableRatio(comparison.buildTimeDeltaRatio),
65
+ routeDeltas: comparison.routeDeltas.map(route => ({
66
+ ...route,
67
+ baselineBytesText: formatBytes(route.baselineBytes),
68
+ currentBytesText: formatBytes(route.currentBytes),
69
+ deltaBytesText: formatBytes(Math.abs(route.deltaBytes)),
70
+ deltaDirection: route.deltaBytes >= 0 ? 'growth' : 'shrink',
71
+ deltaRatioText: formatNullableRatio(route.deltaRatio),
72
+ })),
73
+ chunkDeltas: comparison.chunkDeltas.map(chunk => ({
74
+ ...chunk,
75
+ baselineBytesText: formatBytes(chunk.baselineBytes),
76
+ currentBytesText: formatBytes(chunk.currentBytes),
77
+ deltaBytesText: formatBytes(Math.abs(chunk.deltaBytes)),
78
+ deltaDirection: chunk.deltaBytes >= 0 ? 'growth' : 'shrink',
79
+ deltaRatioText: formatNullableRatio(chunk.deltaRatio),
80
+ })),
81
+ }, null, 2),
82
+ }],
83
+ };
84
+ });
85
+ }
86
+ //# sourceMappingURL=compare-builds.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare-builds.js","sourceRoot":"","sources":["../../src/tools/compare-builds.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerGetLargestRoutes(server: McpServer): void;
3
+ //# sourceMappingURL=get-largest-routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-largest-routes.d.ts","sourceRoot":"","sources":["../../src/tools/get-largest-routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAOpE,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA+ChE"}
@@ -0,0 +1,51 @@
1
+ import { z } from 'zod';
2
+ import { formatBytes, formatMs, formatPct } from '../format.js';
3
+ import { getLargestRoutes } from '../parser/analysis.js';
4
+ import { getBuildStats, listBuildStats } from '../store.js';
5
+ export function registerGetLargestRoutes(server) {
6
+ server.registerTool('get_largest_routes', {
7
+ title: 'Get Largest Routes',
8
+ description: 'Rank the heaviest user-facing routes in a loaded Next.js build by emitted chunk bytes.',
9
+ inputSchema: {
10
+ buildId: z.string().optional().describe('Build ID returned from load_build_stats. Omit to list loaded builds.'),
11
+ limit: z.number().int().positive().max(25).optional().describe('How many routes to include. Defaults to 10.'),
12
+ },
13
+ }, async ({ buildId, limit }) => {
14
+ if (!buildId) {
15
+ return {
16
+ content: [{
17
+ type: 'text',
18
+ text: JSON.stringify({
19
+ builds: listBuildStats().map(build => ({
20
+ ...build,
21
+ totalChunkBytesText: formatBytes(build.totalChunkBytes),
22
+ buildTimeText: formatMs(build.buildTimeMs),
23
+ })),
24
+ }, null, 2),
25
+ }],
26
+ };
27
+ }
28
+ const build = getBuildStats(buildId);
29
+ if (!build) {
30
+ throw new Error(`Build "${buildId}" not found. Call get_largest_routes without a buildId to list all loaded builds.`);
31
+ }
32
+ const routes = getLargestRoutes(build, limit ?? 10);
33
+ return {
34
+ content: [{
35
+ type: 'text',
36
+ text: JSON.stringify({
37
+ buildId: build.id,
38
+ routes: routes.map(route => ({
39
+ ...route,
40
+ totalBytesText: formatBytes(route.totalBytes),
41
+ initialLoadBytesText: formatBytes(route.initialLoadBytes),
42
+ sharedChunkBytesText: formatBytes(route.sharedChunkBytes),
43
+ exclusiveChunkBytesText: formatBytes(route.exclusiveChunkBytes),
44
+ sharedRatioText: formatPct(route.sharedRatio),
45
+ })),
46
+ }, null, 2),
47
+ }],
48
+ };
49
+ });
50
+ }
51
+ //# sourceMappingURL=get-largest-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-largest-routes.js","sourceRoot":"","sources":["../../src/tools/get-largest-routes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE5D,MAAM,UAAU,wBAAwB,CAAC,MAAiB;IACxD,MAAM,CAAC,YAAY,CAAC,oBAAoB,EAAE;QACxC,KAAK,EAAE,oBAAoB;QAC3B,WAAW,EAAE,wFAAwF;QACrG,WAAW,EAAE;YACX,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sEAAsE,CAAC;YAC/G,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,6CAA6C,CAAC;SAC9G;KACF,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE;QAC9B,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO;gBACL,OAAO,EAAE,CAAC;wBACR,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;4BACnB,MAAM,EAAE,cAAc,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;gCACrC,GAAG,KAAK;gCACR,mBAAmB,EAAE,WAAW,CAAC,KAAK,CAAC,eAAe,CAAC;gCACvD,aAAa,EAAE,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC;6BAC3C,CAAC,CAAC;yBACJ,EAAE,IAAI,EAAE,CAAC,CAAC;qBACZ,CAAC;aACH,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,UAAU,OAAO,mFAAmF,CAAC,CAAC;QACxH,CAAC;QAED,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QACpD,OAAO;YACL,OAAO,EAAE,CAAC;oBACR,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;wBACnB,OAAO,EAAE,KAAK,CAAC,EAAE;wBACjB,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;4BAC3B,GAAG,KAAK;4BACR,cAAc,EAAE,WAAW,CAAC,KAAK,CAAC,UAAU,CAAC;4BAC7C,oBAAoB,EAAE,WAAW,CAAC,KAAK,CAAC,gBAAgB,CAAC;4BACzD,oBAAoB,EAAE,WAAW,CAAC,KAAK,CAAC,gBAAgB,CAAC;4BACzD,uBAAuB,EAAE,WAAW,CAAC,KAAK,CAAC,mBAAmB,CAAC;4BAC/D,eAAe,EAAE,SAAS,CAAC,KAAK,CAAC,WAAW,CAAC;yBAC9C,CAAC,CAAC;qBACJ,EAAE,IAAI,EAAE,CAAC,CAAC;iBACZ,CAAC;SACH,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerGetSharedChunks(server: McpServer): void;
3
+ //# sourceMappingURL=get-shared-chunks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-shared-chunks.d.ts","sourceRoot":"","sources":["../../src/tools/get-shared-chunks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAOpE,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA4C/D"}
@@ -0,0 +1,48 @@
1
+ import { z } from 'zod';
2
+ import { formatBytes, formatMs, formatPct } from '../format.js';
3
+ import { getSharedChunks } from '../parser/analysis.js';
4
+ import { getBuildStats, listBuildStats } from '../store.js';
5
+ export function registerGetSharedChunks(server) {
6
+ server.registerTool('get_shared_chunks', {
7
+ title: 'Get Shared Chunks',
8
+ description: 'Rank the heaviest shared chunks in a loaded Next.js build and show which routes depend on them.',
9
+ inputSchema: {
10
+ buildId: z.string().optional().describe('Build ID returned from load_build_stats. Omit to list loaded builds.'),
11
+ limit: z.number().int().positive().max(25).optional().describe('How many shared chunks to include. Defaults to 10.'),
12
+ },
13
+ }, async ({ buildId, limit }) => {
14
+ if (!buildId) {
15
+ return {
16
+ content: [{
17
+ type: 'text',
18
+ text: JSON.stringify({
19
+ builds: listBuildStats().map(build => ({
20
+ ...build,
21
+ totalChunkBytesText: formatBytes(build.totalChunkBytes),
22
+ buildTimeText: formatMs(build.buildTimeMs),
23
+ })),
24
+ }, null, 2),
25
+ }],
26
+ };
27
+ }
28
+ const build = getBuildStats(buildId);
29
+ if (!build) {
30
+ throw new Error(`Build "${buildId}" not found. Call get_shared_chunks without a buildId to list all loaded builds.`);
31
+ }
32
+ const chunks = getSharedChunks(build, limit ?? 10);
33
+ return {
34
+ content: [{
35
+ type: 'text',
36
+ text: JSON.stringify({
37
+ buildId: build.id,
38
+ chunks: chunks.map(chunk => ({
39
+ ...chunk,
40
+ sizeBytesText: formatBytes(chunk.sizeBytes),
41
+ shareOfAllChunkBytesText: formatPct(chunk.shareOfAllChunkBytes),
42
+ })),
43
+ }, null, 2),
44
+ }],
45
+ };
46
+ });
47
+ }
48
+ //# sourceMappingURL=get-shared-chunks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-shared-chunks.js","sourceRoot":"","sources":["../../src/tools/get-shared-chunks.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE5D,MAAM,UAAU,uBAAuB,CAAC,MAAiB;IACvD,MAAM,CAAC,YAAY,CAAC,mBAAmB,EAAE;QACvC,KAAK,EAAE,mBAAmB;QAC1B,WAAW,EAAE,iGAAiG;QAC9G,WAAW,EAAE;YACX,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sEAAsE,CAAC;YAC/G,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oDAAoD,CAAC;SACrH;KACF,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE;QAC9B,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO;gBACL,OAAO,EAAE,CAAC;wBACR,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;4BACnB,MAAM,EAAE,cAAc,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;gCACrC,GAAG,KAAK;gCACR,mBAAmB,EAAE,WAAW,CAAC,KAAK,CAAC,eAAe,CAAC;gCACvD,aAAa,EAAE,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC;6BAC3C,CAAC,CAAC;yBACJ,EAAE,IAAI,EAAE,CAAC,CAAC;qBACZ,CAAC;aACH,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,UAAU,OAAO,kFAAkF,CAAC,CAAC;QACvH,CAAC;QAED,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QACnD,OAAO;YACL,OAAO,EAAE,CAAC;oBACR,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;wBACnB,OAAO,EAAE,KAAK,CAAC,EAAE;wBACjB,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;4BAC3B,GAAG,KAAK;4BACR,aAAa,EAAE,WAAW,CAAC,KAAK,CAAC,SAAS,CAAC;4BAC3C,wBAAwB,EAAE,SAAS,CAAC,KAAK,CAAC,oBAAoB,CAAC;yBAChE,CAAC,CAAC;qBACJ,EAAE,IAAI,EAAE,CAAC,CAAC;iBACZ,CAAC;SACH,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerLoadBuildStats(server: McpServer): void;
3
+ //# sourceMappingURL=load-build-stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-build-stats.d.ts","sourceRoot":"","sources":["../../src/tools/load-build-stats.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAQpE,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAyB9D"}
@@ -0,0 +1,31 @@
1
+ import { z } from 'zod';
2
+ import { formatBytes, formatMs } from '../format.js';
3
+ import { getBuildSummary } from '../parser/analysis.js';
4
+ import { parseBuildStats } from '../parser/build-stats.js';
5
+ import { storeBuildStats } from '../store.js';
6
+ export function registerLoadBuildStats(server) {
7
+ server.registerTool('load_build_stats', {
8
+ title: 'Load Build Stats',
9
+ description: 'Parse a Next.js .next directory and load route and chunk footprint data for later analysis.',
10
+ inputSchema: {
11
+ buildDir: z.string().describe('Absolute or relative path to the Next.js .next build directory'),
12
+ buildOutputPath: z.string().optional().describe('Optional path to captured next build terminal output for deriving build duration'),
13
+ },
14
+ }, async ({ buildDir, buildOutputPath }) => {
15
+ const build = await parseBuildStats(buildDir, buildOutputPath);
16
+ storeBuildStats(build);
17
+ const summary = getBuildSummary(build);
18
+ return {
19
+ content: [{
20
+ type: 'text',
21
+ text: JSON.stringify({
22
+ ...summary,
23
+ totalChunkBytesText: formatBytes(summary.totalChunkBytes),
24
+ sharedChunkBytesText: formatBytes(summary.sharedChunkBytes),
25
+ buildTimeText: formatMs(summary.buildTimeMs),
26
+ }, null, 2),
27
+ }],
28
+ };
29
+ });
30
+ }
31
+ //# sourceMappingURL=load-build-stats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-build-stats.js","sourceRoot":"","sources":["../../src/tools/load-build-stats.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,UAAU,sBAAsB,CAAC,MAAiB;IACtD,MAAM,CAAC,YAAY,CAAC,kBAAkB,EAAE;QACtC,KAAK,EAAE,kBAAkB;QACzB,WAAW,EAAE,6FAA6F;QAC1G,WAAW,EAAE;YACX,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gEAAgE,CAAC;YAC/F,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kFAAkF,CAAC;SACpI;KACF,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,eAAe,EAAE,EAAE,EAAE;QACzC,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;QAC/D,eAAe,CAAC,KAAK,CAAC,CAAC;QAEvB,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;QACvC,OAAO;YACL,OAAO,EAAE,CAAC;oBACR,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;wBACnB,GAAG,OAAO;wBACV,mBAAmB,EAAE,WAAW,CAAC,OAAO,CAAC,eAAe,CAAC;wBACzD,oBAAoB,EAAE,WAAW,CAAC,OAAO,CAAC,gBAAgB,CAAC;wBAC3D,aAAa,EAAE,QAAQ,CAAC,OAAO,CAAC,WAAW,CAAC;qBAC7C,EAAE,IAAI,EAAE,CAAC,CAAC;iBACZ,CAAC;SACH,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@perfonext/build-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for analyzing Next.js build artifacts, route bundle footprint, and shared chunks",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "perfonext-build-mcp": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "engines": {
19
+ "node": ">=18"
20
+ },
21
+ "scripts": {
22
+ "build": "tsc && chmod +x dist/index.js",
23
+ "dev": "tsc --watch",
24
+ "test": "vitest run",
25
+ "inspector": "npx @modelcontextprotocol/inspector node dist/index.js",
26
+ "prepublishOnly": "npm run build"
27
+ },
28
+ "keywords": [
29
+ "mcp",
30
+ "github-copilot",
31
+ "nextjs",
32
+ "build",
33
+ "bundle",
34
+ "performance",
35
+ "route-size",
36
+ "chunk-analysis"
37
+ ],
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/souvikxcoder/perfonext-build-mcp.git"
41
+ },
42
+ "bugs": {
43
+ "url": "https://github.com/souvikxcoder/perfonext-build-mcp/issues"
44
+ },
45
+ "homepage": "https://github.com/souvikxcoder/perfonext-build-mcp#readme",
46
+ "license": "MIT",
47
+ "dependencies": {
48
+ "@modelcontextprotocol/sdk": "^1.29.0",
49
+ "zod": "^4.4.1"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^25.6.0",
53
+ "typescript": "^6.0.3",
54
+ "vitest": "^3.2.4"
55
+ }
56
+ }