@perfonext/build-mcp 0.2.0 → 0.4.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 (46) hide show
  1. package/README.md +42 -1
  2. package/dist/index.js +13 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/parser/analysis.d.ts +7 -1
  5. package/dist/parser/analysis.d.ts.map +1 -1
  6. package/dist/parser/analysis.js +202 -0
  7. package/dist/parser/analysis.js.map +1 -1
  8. package/dist/parser/types.d.ts +88 -0
  9. package/dist/parser/types.d.ts.map +1 -1
  10. package/dist/parser/webpack-stats.d.ts +30 -0
  11. package/dist/parser/webpack-stats.d.ts.map +1 -0
  12. package/dist/parser/webpack-stats.js +353 -0
  13. package/dist/parser/webpack-stats.js.map +1 -0
  14. package/dist/store.d.ts +3 -1
  15. package/dist/store.d.ts.map +1 -1
  16. package/dist/store.js +7 -0
  17. package/dist/store.js.map +1 -1
  18. package/dist/tools/explain-shared-chunks.d.ts +3 -0
  19. package/dist/tools/explain-shared-chunks.d.ts.map +1 -0
  20. package/dist/tools/explain-shared-chunks.js +45 -0
  21. package/dist/tools/explain-shared-chunks.js.map +1 -0
  22. package/dist/tools/find-duplicates.d.ts +3 -0
  23. package/dist/tools/find-duplicates.d.ts.map +1 -0
  24. package/dist/tools/find-duplicates.js +43 -0
  25. package/dist/tools/find-duplicates.js.map +1 -0
  26. package/dist/tools/how-to-collect-stats.d.ts +3 -0
  27. package/dist/tools/how-to-collect-stats.d.ts.map +1 -0
  28. package/dist/tools/how-to-collect-stats.js +137 -0
  29. package/dist/tools/how-to-collect-stats.js.map +1 -0
  30. package/dist/tools/load-webpack-stats.d.ts +3 -0
  31. package/dist/tools/load-webpack-stats.d.ts.map +1 -0
  32. package/dist/tools/load-webpack-stats.js +51 -0
  33. package/dist/tools/load-webpack-stats.js.map +1 -0
  34. package/dist/tools/suggest-optimizations.d.ts +3 -0
  35. package/dist/tools/suggest-optimizations.d.ts.map +1 -0
  36. package/dist/tools/suggest-optimizations.js +46 -0
  37. package/dist/tools/suggest-optimizations.js.map +1 -0
  38. package/dist/tools/trace-import.d.ts +3 -0
  39. package/dist/tools/trace-import.d.ts.map +1 -0
  40. package/dist/tools/trace-import.js +41 -0
  41. package/dist/tools/trace-import.js.map +1 -0
  42. package/dist/tools/webpack-shared.d.ts +21 -0
  43. package/dist/tools/webpack-shared.d.ts.map +1 -0
  44. package/dist/tools/webpack-shared.js +33 -0
  45. package/dist/tools/webpack-shared.js.map +1 -0
  46. package/package.json +1 -1
@@ -0,0 +1,137 @@
1
+ import { z } from 'zod';
2
+ const NEXT_CONFIG_WEBPACK_SNIPPET = `// next.config.ts — write .next/stats.json only when ANALYZE=true
3
+ import type { NextConfig } from "next";
4
+ import { StatsWriterPlugin } from "webpack-stats-plugin";
5
+
6
+ const nextConfig: NextConfig = {
7
+ webpack(config) {
8
+ if (process.env.ANALYZE === "true") {
9
+ config.plugins.push(
10
+ new StatsWriterPlugin({
11
+ filename: "stats.json", // -> .next/stats.json
12
+ // ids + large modulesSpace are required: without them webpack drops
13
+ // chunk ids and collapses the module list, yielding empty attribution.
14
+ stats: {
15
+ all: false,
16
+ modules: true,
17
+ chunks: true,
18
+ chunkModules: true,
19
+ reasons: true,
20
+ ids: true,
21
+ nestedModules: true,
22
+ modulesSpace: Infinity,
23
+ chunkModulesSpace: Infinity,
24
+ },
25
+ }),
26
+ );
27
+ }
28
+ return config;
29
+ },
30
+ };
31
+
32
+ export default nextConfig;`;
33
+ function buildManualResponse(scenario) {
34
+ if (scenario === 'turbopack') {
35
+ return turbopackResponse();
36
+ }
37
+ return {
38
+ method: 'manual',
39
+ steps: [
40
+ {
41
+ step: 1,
42
+ title: 'Add the dev dependency',
43
+ command: 'npm install --save-dev webpack-stats-plugin',
44
+ },
45
+ {
46
+ step: 2,
47
+ title: 'Emit stats behind an ANALYZE flag in next.config',
48
+ snippet: NEXT_CONFIG_WEBPACK_SNIPPET,
49
+ },
50
+ {
51
+ step: 3,
52
+ title: 'Build with the flag set',
53
+ command: 'ANALYZE=true next build',
54
+ },
55
+ ],
56
+ producesFile: '.next/stats.json',
57
+ nextStep: 'Once .next/stats.json exists, call load_webpack_stats with the same buildDir and the buildId from load_build_stats.',
58
+ };
59
+ }
60
+ function buildAutomaticResponse(scenario) {
61
+ if (scenario === 'turbopack') {
62
+ return turbopackResponse();
63
+ }
64
+ return {
65
+ method: 'automatic',
66
+ actions: [
67
+ {
68
+ action: 'add-dev-dependency',
69
+ run: 'npm install --save-dev webpack-stats-plugin',
70
+ },
71
+ {
72
+ action: 'edit-next-config',
73
+ description: 'Add a webpack hook gated behind ANALYZE=true that writes .next/stats.json.',
74
+ snippet: NEXT_CONFIG_WEBPACK_SNIPPET,
75
+ },
76
+ {
77
+ action: 'add-package-script',
78
+ script: { analyze: 'ANALYZE=true next build' },
79
+ },
80
+ {
81
+ action: 'run-build',
82
+ run: 'npm run analyze',
83
+ },
84
+ {
85
+ action: 'verify-output',
86
+ description: 'Confirm .next/stats.json exists before loading it.',
87
+ },
88
+ ],
89
+ producesFile: '.next/stats.json',
90
+ nextStep: 'After .next/stats.json exists, call load_webpack_stats with the same buildDir and the buildId from load_build_stats.',
91
+ };
92
+ }
93
+ function turbopackResponse() {
94
+ return {
95
+ method: 'unavailable',
96
+ scenario: 'turbopack',
97
+ summary: 'Turbopack has no webpack module graph, so .next/stats.json cannot be produced and trace_import cannot run.',
98
+ guidance: 'Run a one-off webpack build (omit --turbopack) with the stats hook to use trace_import, or stay on the manifest-only tools.',
99
+ manifestOnlyTools: [
100
+ 'load_build_stats',
101
+ 'get_largest_routes',
102
+ 'get_shared_chunks',
103
+ 'compare_builds',
104
+ 'explain_growth',
105
+ ],
106
+ nextStep: 'Rebuild with webpack to produce .next/stats.json, or continue with get_largest_routes / get_shared_chunks on the build you already loaded.',
107
+ };
108
+ }
109
+ export function registerHowToCollectStats(server) {
110
+ server.registerTool('how_to_collect_stats', {
111
+ title: 'How To Collect Webpack Stats',
112
+ description: 'Explain how to generate the webpack stats file (.next/stats.json) required by the bundle attribution ' +
113
+ 'tools. Choose manual (a recipe you apply yourself) or automatic (an action plan Copilot executes).',
114
+ inputSchema: {
115
+ method: z
116
+ .enum(['manual', 'automatic'])
117
+ .describe('manual: return a recipe to apply yourself. automatic: return an action plan for Copilot to execute.'),
118
+ scenario: z
119
+ .enum(['webpack', 'turbopack'])
120
+ .optional()
121
+ .describe('Collection context. Defaults to webpack. Use turbopack if the app builds with --turbopack.'),
122
+ },
123
+ }, async ({ method, scenario }) => {
124
+ const resolvedScenario = scenario ?? 'webpack';
125
+ const resolvedMethod = method;
126
+ const payload = resolvedMethod === 'manual'
127
+ ? buildManualResponse(resolvedScenario)
128
+ : buildAutomaticResponse(resolvedScenario);
129
+ return {
130
+ content: [{
131
+ type: 'text',
132
+ text: JSON.stringify(payload, null, 2),
133
+ }],
134
+ };
135
+ });
136
+ }
137
+ //# sourceMappingURL=how-to-collect-stats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"how-to-collect-stats.js","sourceRoot":"","sources":["../../src/tools/how-to-collect-stats.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerLoadWebpackStats(server: McpServer): void;
3
+ //# sourceMappingURL=load-webpack-stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-webpack-stats.d.ts","sourceRoot":"","sources":["../../src/tools/load-webpack-stats.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAOpE,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAiDhE"}
@@ -0,0 +1,51 @@
1
+ import { z } from 'zod';
2
+ import { parseWebpackStats } from '../parser/webpack-stats.js';
3
+ import { getBuildStats, storeWebpackStats } from '../store.js';
4
+ import { statsTextResult } from './webpack-shared.js';
5
+ export function registerLoadWebpackStats(server) {
6
+ server.registerTool('load_webpack_stats', {
7
+ title: 'Load Webpack Stats',
8
+ description: 'Parse the webpack module stats file (.next/stats.json) and link it to a build loaded with ' +
9
+ 'load_build_stats. Unlocks the stats-powered tools: suggest_optimizations (enriched), ' +
10
+ 'find_duplicates, explain_shared_chunks, and trace_import.',
11
+ inputSchema: {
12
+ buildId: z.string().describe('Build ID returned by load_build_stats; the stats.json is read from that build directory'),
13
+ },
14
+ }, async ({ buildId }) => {
15
+ const build = getBuildStats(buildId);
16
+ if (!build) {
17
+ throw new Error(`Build "${buildId}" not found. Call load_build_stats first and pass the buildId it returns.`);
18
+ }
19
+ const stats = await parseWebpackStats(build.buildDir, buildId);
20
+ if (!stats) {
21
+ return statsTextResult({
22
+ buildId,
23
+ webpackStatsLoaded: false,
24
+ message: `No stats.json found in ${build.buildDir}. A stock next build does not emit one.`,
25
+ nextStep: 'Call how_to_collect_stats to generate .next/stats.json, then load_webpack_stats again.',
26
+ });
27
+ }
28
+ storeWebpackStats(stats);
29
+ const looksCollapsed = stats.parsedModuleCount === 0 || stats.chunks.length === 0;
30
+ return statsTextResult({
31
+ buildId,
32
+ webpackStatsLoaded: true,
33
+ statsPath: stats.statsPath,
34
+ moduleCount: stats.moduleCount,
35
+ parsedModuleCount: stats.parsedModuleCount,
36
+ chunkCount: stats.chunks.length,
37
+ ...(looksCollapsed
38
+ ? {
39
+ warning: 'The stats file parsed but contains no usable modules/chunks. This usually means the stats ' +
40
+ 'config collapsed the module graph (webpack groups modules once `modulesSpace` is exceeded and ' +
41
+ 'omits chunk ids unless `ids: true`). Re-run how_to_collect_stats for the corrected config and rebuild.',
42
+ }
43
+ : {}),
44
+ nextStep: looksCollapsed
45
+ ? 'Call how_to_collect_stats again, apply the corrected stats config, rebuild, then load_webpack_stats.'
46
+ : 'Now call suggest_optimizations for ranked, evidence-backed fixes (now enriched with this stats data). ' +
47
+ 'Drill in with find_duplicates, explain_shared_chunks, or trace_import on a specific package.',
48
+ });
49
+ });
50
+ }
51
+ //# sourceMappingURL=load-webpack-stats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-webpack-stats.js","sourceRoot":"","sources":["../../src/tools/load-webpack-stats.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,MAAM,UAAU,wBAAwB,CAAC,MAAiB;IACxD,MAAM,CAAC,YAAY,CAAC,oBAAoB,EAAE;QACxC,KAAK,EAAE,oBAAoB;QAC3B,WAAW,EACT,4FAA4F;YAC5F,uFAAuF;YACvF,2DAA2D;QAC7D,WAAW,EAAE;YACX,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,yFAAyF,CAAC;SACxH;KACF,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;QACvB,MAAM,KAAK,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,UAAU,OAAO,2EAA2E,CAAC,CAAC;QAChH,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,iBAAiB,CAAC,KAAK,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC/D,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,eAAe,CAAC;gBACrB,OAAO;gBACP,kBAAkB,EAAE,KAAK;gBACzB,OAAO,EAAE,0BAA0B,KAAK,CAAC,QAAQ,yCAAyC;gBAC1F,QAAQ,EAAE,wFAAwF;aACnG,CAAC,CAAC;QACL,CAAC;QAED,iBAAiB,CAAC,KAAK,CAAC,CAAC;QACzB,MAAM,cAAc,GAAG,KAAK,CAAC,iBAAiB,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC;QAClF,OAAO,eAAe,CAAC;YACrB,OAAO;YACP,kBAAkB,EAAE,IAAI;YACxB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;YAC1C,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM;YAC/B,GAAG,CAAC,cAAc;gBAChB,CAAC,CAAC;oBACE,OAAO,EACL,4FAA4F;wBAC5F,gGAAgG;wBAChG,wGAAwG;iBAC3G;gBACH,CAAC,CAAC,EAAE,CAAC;YACP,QAAQ,EAAE,cAAc;gBACtB,CAAC,CAAC,sGAAsG;gBACxG,CAAC,CAAC,wGAAwG;oBACxG,8FAA8F;SACnG,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerSuggestOptimizations(server: McpServer): void;
3
+ //# sourceMappingURL=suggest-optimizations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggest-optimizations.d.ts","sourceRoot":"","sources":["../../src/tools/suggest-optimizations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAQpE,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA0CpE"}
@@ -0,0 +1,46 @@
1
+ import { z } from 'zod';
2
+ import { formatBytes } from '../format.js';
3
+ import { suggestOptimizations } from '../parser/analysis.js';
4
+ import { getBuildStats, getWebpackStats } from '../store.js';
5
+ import { statsTextResult } from './webpack-shared.js';
6
+ export function registerSuggestOptimizations(server) {
7
+ server.registerTool('suggest_optimizations', {
8
+ title: 'Suggest Optimizations',
9
+ description: 'Aggregate route, chunk, and (when loaded) webpack-stats evidence into severity-ranked, ' +
10
+ 'evidence-backed bundle optimizations tied to concrete Next.js actions. Works on manifests alone; ' +
11
+ 'load_webpack_stats first for dedupe, shared-chunk, and package-import suggestions.',
12
+ inputSchema: {
13
+ buildId: z.string().describe('Build ID returned by load_build_stats'),
14
+ limit: z.number().int().positive().max(50).optional().describe('Maximum suggestions to return. Defaults to 15.'),
15
+ },
16
+ }, async ({ buildId, limit }) => {
17
+ const build = getBuildStats(buildId);
18
+ if (!build) {
19
+ throw new Error(`Build "${buildId}" not found. Call load_build_stats first.`);
20
+ }
21
+ const stats = getWebpackStats(buildId) ?? null;
22
+ const report = suggestOptimizations(build, stats, limit ?? 15);
23
+ return statsTextResult({
24
+ buildId,
25
+ webpackStatsUsed: report.webpackStatsUsed,
26
+ suggestionCount: report.suggestionCount,
27
+ suggestions: report.suggestions.map(suggestion => ({
28
+ kind: suggestion.kind,
29
+ severity: suggestion.severity,
30
+ title: suggestion.title,
31
+ bytes: suggestion.bytes,
32
+ bytesText: formatBytes(suggestion.bytes),
33
+ evidence: suggestion.evidence,
34
+ recommendedAction: suggestion.recommendedAction,
35
+ packageName: suggestion.packageName,
36
+ chunkPath: suggestion.chunkPath,
37
+ routePath: suggestion.routePath,
38
+ })),
39
+ ...(report.note ? { note: report.note } : {}),
40
+ nextStep: report.webpackStatsUsed
41
+ ? 'Use trace_import on a flagged package to see its import chain, or find_duplicates / explain_shared_chunks for more detail.'
42
+ : 'Run how_to_collect_stats then load_webpack_stats to unlock dedupe, shared-chunk, and package-import suggestions.',
43
+ });
44
+ });
45
+ }
46
+ //# sourceMappingURL=suggest-optimizations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggest-optimizations.js","sourceRoot":"","sources":["../../src/tools/suggest-optimizations.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,MAAM,UAAU,4BAA4B,CAAC,MAAiB;IAC5D,MAAM,CAAC,YAAY,CAAC,uBAAuB,EAAE;QAC3C,KAAK,EAAE,uBAAuB;QAC9B,WAAW,EACT,yFAAyF;YACzF,mGAAmG;YACnG,oFAAoF;QACtF,WAAW,EAAE;YACX,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;YACrE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gDAAgD,CAAC;SACjH;KACF,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE;QAC9B,MAAM,KAAK,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,UAAU,OAAO,2CAA2C,CAAC,CAAC;QAChF,CAAC;QAED,MAAM,KAAK,GAAG,eAAe,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;QAC/C,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QAE/D,OAAO,eAAe,CAAC;YACrB,OAAO;YACP,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;YACzC,eAAe,EAAE,MAAM,CAAC,eAAe;YACvC,WAAW,EAAE,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;gBACjD,IAAI,EAAE,UAAU,CAAC,IAAI;gBACrB,QAAQ,EAAE,UAAU,CAAC,QAAQ;gBAC7B,KAAK,EAAE,UAAU,CAAC,KAAK;gBACvB,KAAK,EAAE,UAAU,CAAC,KAAK;gBACvB,SAAS,EAAE,WAAW,CAAC,UAAU,CAAC,KAAK,CAAC;gBACxC,QAAQ,EAAE,UAAU,CAAC,QAAQ;gBAC7B,iBAAiB,EAAE,UAAU,CAAC,iBAAiB;gBAC/C,WAAW,EAAE,UAAU,CAAC,WAAW;gBACnC,SAAS,EAAE,UAAU,CAAC,SAAS;gBAC/B,SAAS,EAAE,UAAU,CAAC,SAAS;aAChC,CAAC,CAAC;YACH,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7C,QAAQ,EAAE,MAAM,CAAC,gBAAgB;gBAC/B,CAAC,CAAC,4HAA4H;gBAC9H,CAAC,CAAC,kHAAkH;SACvH,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerTraceImport(server: McpServer): void;
3
+ //# sourceMappingURL=trace-import.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace-import.d.ts","sourceRoot":"","sources":["../../src/tools/trace-import.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAQpE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAqC3D"}
@@ -0,0 +1,41 @@
1
+ import { z } from 'zod';
2
+ import { formatBytes } from '../format.js';
3
+ import { traceImport } from '../parser/webpack-stats.js';
4
+ import { getBuildStats } from '../store.js';
5
+ import { resolveWebpackStats, statsTextResult } from './webpack-shared.js';
6
+ export function registerTraceImport(server) {
7
+ server.registerTool('trace_import', {
8
+ title: 'Trace Import',
9
+ description: 'Explain why a module is bundled by tracing its import chain from an entry point to the module. ' +
10
+ 'Requires load_webpack_stats first.',
11
+ inputSchema: {
12
+ buildId: z.string().describe('Build ID returned by load_build_stats'),
13
+ moduleName: z.string().describe('Module or package name to search for (case-insensitive substring), e.g. "lodash"'),
14
+ limit: z.number().int().positive().max(25).optional().describe('Maximum matching modules to trace. Defaults to 10.'),
15
+ },
16
+ }, async ({ buildId, moduleName, limit }) => {
17
+ const build = getBuildStats(buildId);
18
+ if (!build) {
19
+ throw new Error(`Build "${buildId}" not found. Call load_build_stats first.`);
20
+ }
21
+ const resolved = resolveWebpackStats(buildId);
22
+ if ('breadcrumb' in resolved) {
23
+ return statsTextResult(resolved.breadcrumb);
24
+ }
25
+ const result = traceImport(resolved.stats, moduleName, limit ?? 10);
26
+ return statsTextResult({
27
+ buildId,
28
+ query: result.query,
29
+ matchCount: result.matchCount,
30
+ traces: result.traces.map(trace => ({
31
+ moduleName: trace.moduleName,
32
+ packageName: trace.packageName,
33
+ sizeBytes: trace.sizeBytes,
34
+ sizeBytesText: formatBytes(trace.sizeBytes),
35
+ chunkFiles: trace.chunkFiles,
36
+ importChain: trace.importChain,
37
+ })),
38
+ });
39
+ });
40
+ }
41
+ //# sourceMappingURL=trace-import.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace-import.js","sourceRoot":"","sources":["../../src/tools/trace-import.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3E,MAAM,UAAU,mBAAmB,CAAC,MAAiB;IACnD,MAAM,CAAC,YAAY,CAAC,cAAc,EAAE;QAClC,KAAK,EAAE,cAAc;QACrB,WAAW,EACT,iGAAiG;YACjG,oCAAoC;QACtC,WAAW,EAAE;YACX,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;YACrE,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,kFAAkF,CAAC;YACnH,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,UAAU,EAAE,KAAK,EAAE,EAAE,EAAE;QAC1C,MAAM,KAAK,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,UAAU,OAAO,2CAA2C,CAAC,CAAC;QAChF,CAAC;QAED,MAAM,QAAQ,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;QAC9C,IAAI,YAAY,IAAI,QAAQ,EAAE,CAAC;YAC7B,OAAO,eAAe,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QAC9C,CAAC;QAED,MAAM,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QACpE,OAAO,eAAe,CAAC;YACrB,OAAO;YACP,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;gBAClC,UAAU,EAAE,KAAK,CAAC,UAAU;gBAC5B,WAAW,EAAE,KAAK,CAAC,WAAW;gBAC9B,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,aAAa,EAAE,WAAW,CAAC,KAAK,CAAC,SAAS,CAAC;gBAC3C,UAAU,EAAE,KAAK,CAAC,UAAU;gBAC5B,WAAW,EAAE,KAAK,CAAC,WAAW;aAC/B,CAAC,CAAC;SACJ,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type { ParsedWebpackStats } from '../parser/types.js';
2
+ /**
3
+ * Resolve loaded webpack stats for a build, or a machine-readable breadcrumb when they are absent.
4
+ * The attribution tools degrade gracefully: a missing stats file is guidance, not an error.
5
+ */
6
+ export declare function resolveWebpackStats(buildId: string): {
7
+ stats: ParsedWebpackStats;
8
+ } | {
9
+ breadcrumb: Record<string, unknown>;
10
+ };
11
+ export declare function statsTextResult(payload: Record<string, unknown>): {
12
+ content: {
13
+ type: "text";
14
+ text: string;
15
+ }[];
16
+ };
17
+ export declare function withBytesText(bytes: number): {
18
+ sizeBytes: number;
19
+ sizeBytesText: string;
20
+ };
21
+ //# sourceMappingURL=webpack-shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webpack-shared.d.ts","sourceRoot":"","sources":["../../src/tools/webpack-shared.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAE7D;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,GACd;IAAE,KAAK,EAAE,kBAAkB,CAAA;CAAE,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAiBzE;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;EAO/D;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAEzF"}
@@ -0,0 +1,33 @@
1
+ import { formatBytes } from '../format.js';
2
+ import { getWebpackStats } from '../store.js';
3
+ /**
4
+ * Resolve loaded webpack stats for a build, or a machine-readable breadcrumb when they are absent.
5
+ * The attribution tools degrade gracefully: a missing stats file is guidance, not an error.
6
+ */
7
+ export function resolveWebpackStats(buildId) {
8
+ const stats = getWebpackStats(buildId);
9
+ if (stats) {
10
+ return { stats };
11
+ }
12
+ return {
13
+ breadcrumb: {
14
+ buildId,
15
+ webpackStatsLoaded: false,
16
+ message: 'Webpack module stats are not loaded for this build, so import-level attribution is unavailable.',
17
+ nextStep: 'Generate .next/stats.json via how_to_collect_stats, then call load_webpack_stats with this buildId. ' +
18
+ 'The manifest-based tools (get_largest_routes, get_shared_chunks, compare_builds, explain_growth) work without it.',
19
+ },
20
+ };
21
+ }
22
+ export function statsTextResult(payload) {
23
+ return {
24
+ content: [{
25
+ type: 'text',
26
+ text: JSON.stringify(payload, null, 2),
27
+ }],
28
+ };
29
+ }
30
+ export function withBytesText(bytes) {
31
+ return { sizeBytes: bytes, sizeBytesText: formatBytes(bytes) };
32
+ }
33
+ //# sourceMappingURL=webpack-shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webpack-shared.js","sourceRoot":"","sources":["../../src/tools/webpack-shared.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAG9C;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CACjC,OAAe;IAEf,MAAM,KAAK,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IACvC,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,EAAE,KAAK,EAAE,CAAC;IACnB,CAAC;IAED,OAAO;QACL,UAAU,EAAE;YACV,OAAO;YACP,kBAAkB,EAAE,KAAK;YACzB,OAAO,EACL,iGAAiG;YACnG,QAAQ,EACN,sGAAsG;gBACtG,mHAAmH;SACtH;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAgC;IAC9D,OAAO;QACL,OAAO,EAAE,CAAC;gBACR,IAAI,EAAE,MAAe;gBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;aACvC,CAAC;KACH,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,aAAa,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;AACjE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@perfonext/build-mcp",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "MCP server for analyzing Next.js build artifacts, route bundle footprint, and shared chunks",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",