@perfonext/build-mcp 0.2.0 → 0.3.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.
package/README.md CHANGED
@@ -12,17 +12,23 @@
12
12
  - compares two builds and explains which routes and chunks drove bundle growth, with
13
13
  severity-ranked, evidence-backed fix suggestions
14
14
  - matches chunks across builds even though Next.js fingerprints filenames with content hashes
15
+ - traces why a given module or npm package is bundled (import chain entry → module) when an
16
+ optional webpack stats file is collected
15
17
  - keeps loaded build snapshots in memory so an MCP client can inspect them without re-reading the same build
16
18
 
17
19
  ## Inputs
18
20
 
19
- The MVP reads build artifacts developers already have after running `next build`:
21
+ The core tools read build artifacts developers already have after running `next build`:
20
22
 
21
23
  - `.next/build-manifest.json`
22
24
  - `.next/prerender-manifest.json` when present
23
25
  - `.next/app-build-manifest.json` when present
24
26
  - optional captured `next build` output text to derive build duration
25
27
 
28
+ Import-level attribution (`trace_import`) additionally needs a webpack module-stats file at
29
+ `.next/stats.json`. A stock `next build` does not emit one; `how_to_collect_stats` returns the recipe
30
+ to generate it. The manifest tools above never read it, so they work with or without it.
31
+
26
32
  ## Tools
27
33
 
28
34
  | Tool | Description |
@@ -32,11 +38,29 @@ The MVP reads build artifacts developers already have after running `next build`
32
38
  | `get_shared_chunks` | Rank shared chunks by size and show which routes depend on them |
33
39
  | `compare_builds` | Compare a baseline and current build snapshot to show which routes and chunks grew or shrank |
34
40
  | `explain_growth` | Severity-rank which routes and chunks drove bundle growth between two builds, with evidence-backed fix suggestions |
41
+ | `how_to_collect_stats` | Return the recipe (manual) or an action plan (automatic) to generate `.next/stats.json` |
42
+ | `load_webpack_stats` | Parse `.next/stats.json` and link it to a loaded build; required before `trace_import` |
43
+ | `trace_import` | Explain why a module or npm package is bundled by walking its import chain to the entry |
35
44
 
36
45
  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.
37
46
 
38
47
  Because Next.js content-hashes emitted filenames (`framework-<hash>.js`, and CSS files named purely by hash), `compare_builds` and `explain_growth` match chunks across builds by a hash-normalized identity. This prevents a rehashed-but-unchanged chunk from being misreported as removed-and-recreated, while still flagging genuinely new chunks.
39
48
 
49
+ ### Deep bundle attribution (optional)
50
+
51
+ The manifest tools work with zero setup. To answer "why is this package bundled?", collect a webpack
52
+ stats file first:
53
+
54
+ 1. Call `how_to_collect_stats({ method: 'manual' | 'automatic' })` and apply the returned steps — it
55
+ adds `webpack-stats-plugin`, gates a `next.config` hook behind `ANALYZE=true`, and rebuilds.
56
+ 2. Call `load_build_stats({ buildDir })` to get a `buildId`.
57
+ 3. Call `load_webpack_stats({ buildId })` to parse the generated `.next/stats.json`.
58
+ 4. Call `trace_import({ buildId, moduleName })` to see the import chain that pulls a module in.
59
+
60
+ If the app builds with Turbopack there is no webpack module graph, so `how_to_collect_stats` says so
61
+ and points back to the manifest-only tools. `trace_import` degrades gracefully with a breadcrumb when
62
+ no stats file is loaded — it is never an error.
63
+
40
64
  ## Install
41
65
 
42
66
  Run directly with `npx`:
@@ -77,6 +101,8 @@ Add this server to VS Code settings:
77
101
  - "Summarize the build footprint and tell me which routes ship the most JavaScript."
78
102
  - "Compare my baseline and current `.next` builds and show me which routes or shared chunks grew the most."
79
103
  - "Explain what grew between my baseline and current `.next` builds and what I should fix first."
104
+ - "Set up webpack stats collection so I can see why a package is bundled."
105
+ - "Why is `axios` in my bundle? Trace its import chain."
80
106
 
81
107
  ## Development
82
108
 
package/dist/index.js CHANGED
@@ -6,15 +6,21 @@ import { registerGetLargestRoutes } from './tools/get-largest-routes.js';
6
6
  import { registerGetSharedChunks } from './tools/get-shared-chunks.js';
7
7
  import { registerCompareBuilds } from './tools/compare-builds.js';
8
8
  import { registerExplainGrowth } from './tools/explain-growth.js';
9
+ import { registerHowToCollectStats } from './tools/how-to-collect-stats.js';
10
+ import { registerLoadWebpackStats } from './tools/load-webpack-stats.js';
11
+ import { registerTraceImport } from './tools/trace-import.js';
9
12
  const server = new McpServer({
10
13
  name: 'perfonext-build-mcp',
11
- version: '0.2.0',
14
+ version: '0.3.0',
12
15
  });
13
16
  registerLoadBuildStats(server);
14
17
  registerGetLargestRoutes(server);
15
18
  registerGetSharedChunks(server);
16
19
  registerCompareBuilds(server);
17
20
  registerExplainGrowth(server);
21
+ registerHowToCollectStats(server);
22
+ registerLoadWebpackStats(server);
23
+ registerTraceImport(server);
18
24
  const transport = new StdioServerTransport();
19
25
  await server.connect(transport);
20
26
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +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;AAClE,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;AAC9B,qBAAqB,CAAC,MAAM,CAAC,CAAC;AAE9B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC"}
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;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,yBAAyB,EAAE,MAAM,iCAAiC,CAAC;AAC5E,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAE9D,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;AAC9B,qBAAqB,CAAC,MAAM,CAAC,CAAC;AAC9B,yBAAyB,CAAC,MAAM,CAAC,CAAC;AAClC,wBAAwB,CAAC,MAAM,CAAC,CAAC;AACjC,mBAAmB,CAAC,MAAM,CAAC,CAAC;AAE5B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC"}
@@ -128,4 +128,45 @@ export interface GrowthExplanation {
128
128
  topGrowingChunks: ChunkGrowthContribution[];
129
129
  suggestions: GrowthSuggestion[];
130
130
  }
131
+ export interface WebpackModuleReason {
132
+ moduleName: string | null;
133
+ userRequest: string | null;
134
+ }
135
+ export interface WebpackModule {
136
+ name: string;
137
+ packageName: string | null;
138
+ sizeBytes: number;
139
+ chunkIds: Array<string | number>;
140
+ reasons: WebpackModuleReason[];
141
+ }
142
+ export interface WebpackChunk {
143
+ id: string | number;
144
+ names: string[];
145
+ files: string[];
146
+ sizeBytes: number;
147
+ }
148
+ export interface ParsedWebpackStats {
149
+ buildId: string;
150
+ statsPath: string;
151
+ modules: WebpackModule[];
152
+ chunks: WebpackChunk[];
153
+ moduleCount: number;
154
+ parsedModuleCount: number;
155
+ }
156
+ export interface ImportChainNode {
157
+ moduleName: string;
158
+ packageName: string | null;
159
+ }
160
+ export interface ImportTrace {
161
+ moduleName: string;
162
+ packageName: string | null;
163
+ sizeBytes: number;
164
+ chunkFiles: string[];
165
+ importChain: ImportChainNode[];
166
+ }
167
+ export interface TraceImportResult {
168
+ query: string;
169
+ matchCount: number;
170
+ traces: ImportTrace[];
171
+ }
131
172
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/parser/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,KAAK,CAAC;AAErD,MAAM,MAAM,sBAAsB,GAAG,KAAK,GAAG,gBAAgB,GAAG,mBAAmB,GAAG,IAAI,CAAC;AAE3F,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;IAChB,sBAAsB,EAAE,sBAAsB,CAAC;IAC/C,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,OAAO,CAAC;IACvB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;IAChB,sBAAsB,EAAE,sBAAsB,CAAC;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;IACvB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,uBAAuB,EAAE,MAAM,CAAC;IAChC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,wBAAwB,EAAE,MAAM,CAAC;IACjC,uBAAuB,EAAE,MAAM,CAAC;IAChC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,WAAW,EAAE,eAAe,EAAE,CAAC;IAC/B,WAAW,EAAE,eAAe,EAAE,CAAC;CAChC;AAED,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,CAAC;AAE7D,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,cAAc,CAAC;IACzB,qBAAqB,EAAE,uBAAuB,EAAE,CAAC;CAClD;AAED,MAAM,WAAW,oBAAoB;IACnC,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,EAAE,cAAc,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,MAAM,oBAAoB,GAC5B,qBAAqB,GACrB,WAAW,GACX,cAAc,GACd,kBAAkB,CAAC;AAEvB,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,oBAAoB,CAAC;IAC3B,QAAQ,EAAE,cAAc,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,oBAAoB,CAAC;IAC9B,aAAa,EAAE,kBAAkB,EAAE,CAAC;IACpC,gBAAgB,EAAE,uBAAuB,EAAE,CAAC;IAC5C,WAAW,EAAE,gBAAgB,EAAE,CAAC;CACjC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/parser/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { ParsedWebpackStats, TraceImportResult } from './types.js';
2
+ /**
3
+ * Resolve the npm package an emitted module belongs to. Uses the LAST `node_modules/` segment so
4
+ * nested installs (`a/node_modules/b`) attribute to the inner package, and preserves scoped names
5
+ * (`@org/pkg`). Returns null for first-party application code.
6
+ */
7
+ export declare function extractPackageName(moduleName: string): string | null;
8
+ /**
9
+ * Parse a webpack module-stats JSON (`.next/stats.json`) into a compact, analysis-ready shape.
10
+ * Only the fields the attribution tools need are retained — raw source, asset maps, and other
11
+ * webpack noise are dropped so a multi-MB stats file stays small in memory.
12
+ */
13
+ export declare function parseWebpackStats(buildDirPath: string, buildId: string): Promise<ParsedWebpackStats | null>;
14
+ export declare function traceImport(stats: ParsedWebpackStats, moduleName: string, limit?: number): TraceImportResult;
15
+ //# sourceMappingURL=webpack-stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webpack-stats.d.ts","sourceRoot":"","sources":["../../src/parser/webpack-stats.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAGV,kBAAkB,EAClB,iBAAiB,EAIlB,MAAM,YAAY,CAAC;AA6CpB;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAoBpE;AA4DD;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CA2CpC;AAmED,wBAAgB,WAAW,CACzB,KAAK,EAAE,kBAAkB,EACzB,UAAU,EAAE,MAAM,EAClB,KAAK,SAAK,GACT,iBAAiB,CAsBnB"}
@@ -0,0 +1,195 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join, resolve } from 'node:path';
3
+ const NODE_MODULES_MARKER = 'node_modules/';
4
+ function isRecord(value) {
5
+ return value !== null && typeof value === 'object';
6
+ }
7
+ function toFiniteNumber(value) {
8
+ return typeof value === 'number' && Number.isFinite(value) ? value : 0;
9
+ }
10
+ function toStringArray(value) {
11
+ if (!Array.isArray(value)) {
12
+ return [];
13
+ }
14
+ return value.filter((item) => typeof item === 'string');
15
+ }
16
+ /**
17
+ * Resolve the npm package an emitted module belongs to. Uses the LAST `node_modules/` segment so
18
+ * nested installs (`a/node_modules/b`) attribute to the inner package, and preserves scoped names
19
+ * (`@org/pkg`). Returns null for first-party application code.
20
+ */
21
+ export function extractPackageName(moduleName) {
22
+ // Webpack `name` uses POSIX separators, but the `identifier` fallback can carry
23
+ // Windows backslashes — normalize so attribution works on every platform.
24
+ const normalized = moduleName.replace(/\\/g, '/');
25
+ const markerIndex = normalized.lastIndexOf(NODE_MODULES_MARKER);
26
+ if (markerIndex === -1) {
27
+ return null;
28
+ }
29
+ const rest = normalized.slice(markerIndex + NODE_MODULES_MARKER.length);
30
+ const parts = rest.split('/').filter(Boolean);
31
+ if (parts.length === 0) {
32
+ return null;
33
+ }
34
+ if (parts[0].startsWith('@') && parts.length >= 2) {
35
+ return `${parts[0]}/${parts[1]}`;
36
+ }
37
+ return parts[0];
38
+ }
39
+ function normalizeReason(raw) {
40
+ if (!isRecord(raw)) {
41
+ return null;
42
+ }
43
+ const moduleName = typeof raw.moduleName === 'string' ? raw.moduleName : null;
44
+ const userRequest = typeof raw.userRequest === 'string' ? raw.userRequest : null;
45
+ if (moduleName === null && userRequest === null) {
46
+ return null;
47
+ }
48
+ return { moduleName, userRequest };
49
+ }
50
+ function normalizeModule(raw) {
51
+ const name = typeof raw.name === 'string'
52
+ ? raw.name
53
+ : typeof raw.identifier === 'string'
54
+ ? raw.identifier
55
+ : null;
56
+ if (!name) {
57
+ return null;
58
+ }
59
+ const chunkIds = Array.isArray(raw.chunks)
60
+ ? raw.chunks.filter((id) => typeof id === 'string' || typeof id === 'number')
61
+ : [];
62
+ const reasons = Array.isArray(raw.reasons)
63
+ ? raw.reasons
64
+ .map(normalizeReason)
65
+ .filter((reason) => reason !== null)
66
+ : [];
67
+ return {
68
+ name,
69
+ packageName: extractPackageName(name),
70
+ sizeBytes: toFiniteNumber(raw.size),
71
+ chunkIds,
72
+ reasons,
73
+ };
74
+ }
75
+ function normalizeChunk(raw) {
76
+ if (raw.id === undefined || (typeof raw.id !== 'string' && typeof raw.id !== 'number')) {
77
+ return null;
78
+ }
79
+ return {
80
+ id: raw.id,
81
+ names: toStringArray(raw.names),
82
+ files: toStringArray(raw.files),
83
+ sizeBytes: toFiniteNumber(raw.size),
84
+ };
85
+ }
86
+ /**
87
+ * Parse a webpack module-stats JSON (`.next/stats.json`) into a compact, analysis-ready shape.
88
+ * Only the fields the attribution tools need are retained — raw source, asset maps, and other
89
+ * webpack noise are dropped so a multi-MB stats file stays small in memory.
90
+ */
91
+ export async function parseWebpackStats(buildDirPath, buildId) {
92
+ const buildDir = resolve(buildDirPath);
93
+ const statsPath = join(buildDir, 'stats.json');
94
+ let content;
95
+ try {
96
+ content = await readFile(statsPath, 'utf-8');
97
+ }
98
+ catch (error) {
99
+ if (error.code === 'ENOENT') {
100
+ return null;
101
+ }
102
+ throw error;
103
+ }
104
+ let raw;
105
+ try {
106
+ raw = JSON.parse(content);
107
+ }
108
+ catch (error) {
109
+ throw new Error(`Failed to parse webpack stats JSON at ${statsPath} (build "${buildId}"): ${error.message}`);
110
+ }
111
+ const rawModules = Array.isArray(raw.modules) ? raw.modules : [];
112
+ const rawChunks = Array.isArray(raw.chunks) ? raw.chunks : [];
113
+ const modules = rawModules
114
+ .map(module => normalizeModule(module))
115
+ .filter((module) => module !== null);
116
+ const chunks = rawChunks
117
+ .map(chunk => normalizeChunk(chunk))
118
+ .filter((chunk) => chunk !== null);
119
+ return {
120
+ buildId,
121
+ statsPath,
122
+ modules,
123
+ chunks,
124
+ moduleCount: rawModules.length,
125
+ parsedModuleCount: modules.length,
126
+ };
127
+ }
128
+ function buildModuleIndex(stats) {
129
+ const index = new Map();
130
+ for (const module of stats.modules) {
131
+ index.set(module.name, module);
132
+ }
133
+ return index;
134
+ }
135
+ function chunkFilesForModule(module, chunkById) {
136
+ const files = new Set();
137
+ for (const chunkId of module.chunkIds) {
138
+ const chunk = chunkById.get(chunkId);
139
+ for (const file of chunk?.files ?? []) {
140
+ files.add(file);
141
+ }
142
+ }
143
+ return Array.from(files).sort();
144
+ }
145
+ /**
146
+ * Walk a module's `reasons` upward to the nearest entry to explain why it is bundled. Picks the
147
+ * first resolvable parent at each step, guards against cycles, and caps depth so a pathological
148
+ * graph cannot loop. Returns the chain ordered from entry → target.
149
+ */
150
+ function buildImportChain(target, moduleIndex) {
151
+ const chain = [
152
+ { moduleName: target.name, packageName: target.packageName },
153
+ ];
154
+ const visited = new Set([target.name]);
155
+ let current = target;
156
+ const maxDepth = 25;
157
+ for (let depth = 0; depth < maxDepth; depth += 1) {
158
+ const parentReason = current.reasons.find(reason => reason.moduleName !== null && !visited.has(reason.moduleName));
159
+ if (!parentReason || parentReason.moduleName === null) {
160
+ break;
161
+ }
162
+ const parent = moduleIndex.get(parentReason.moduleName);
163
+ visited.add(parentReason.moduleName);
164
+ chain.push({
165
+ moduleName: parentReason.moduleName,
166
+ packageName: parent?.packageName ?? extractPackageName(parentReason.moduleName),
167
+ });
168
+ if (!parent || parent.reasons.length === 0) {
169
+ break;
170
+ }
171
+ current = parent;
172
+ }
173
+ return chain.reverse();
174
+ }
175
+ export function traceImport(stats, moduleName, limit = 10) {
176
+ const query = moduleName.toLowerCase();
177
+ const moduleIndex = buildModuleIndex(stats);
178
+ const chunkById = new Map(stats.chunks.map(chunk => [chunk.id, chunk]));
179
+ const matches = stats.modules
180
+ .filter(module => module.name.toLowerCase().includes(query))
181
+ .sort((left, right) => right.sizeBytes - left.sizeBytes);
182
+ const traces = matches.slice(0, limit).map(module => ({
183
+ moduleName: module.name,
184
+ packageName: module.packageName,
185
+ sizeBytes: module.sizeBytes,
186
+ chunkFiles: chunkFilesForModule(module, chunkById),
187
+ importChain: buildImportChain(module, moduleIndex),
188
+ }));
189
+ return {
190
+ query: moduleName,
191
+ matchCount: matches.length,
192
+ traces,
193
+ };
194
+ }
195
+ //# sourceMappingURL=webpack-stats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webpack-stats.js","sourceRoot":"","sources":["../../src/parser/webpack-stats.ts"],"names":[],"mappings":"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"}
package/dist/store.d.ts CHANGED
@@ -1,6 +1,8 @@
1
- import type { ParsedBuildStats } from './parser/types.js';
1
+ import type { ParsedBuildStats, ParsedWebpackStats } from './parser/types.js';
2
2
  export declare function storeBuildStats(build: ParsedBuildStats): void;
3
3
  export declare function getBuildStats(id: string): ParsedBuildStats | undefined;
4
+ export declare function storeWebpackStats(stats: ParsedWebpackStats): void;
5
+ export declare function getWebpackStats(buildId: string): ParsedWebpackStats | undefined;
4
6
  export declare function listBuildStats(): Array<{
5
7
  id: string;
6
8
  buildDir: string;
@@ -1 +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"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAK9E,wBAAgB,eAAe,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAE7D;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAEtE;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAEjE;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAE/E;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 CHANGED
@@ -1,10 +1,17 @@
1
1
  const builds = new Map();
2
+ const webpackStats = new Map();
2
3
  export function storeBuildStats(build) {
3
4
  builds.set(build.id, build);
4
5
  }
5
6
  export function getBuildStats(id) {
6
7
  return builds.get(id);
7
8
  }
9
+ export function storeWebpackStats(stats) {
10
+ webpackStats.set(stats.buildId, stats);
11
+ }
12
+ export function getWebpackStats(buildId) {
13
+ return webpackStats.get(buildId);
14
+ }
8
15
  export function listBuildStats() {
9
16
  return Array.from(builds.values()).map(build => ({
10
17
  id: build.id,
package/dist/store.js.map CHANGED
@@ -1 +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"}
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,GAAG,IAAI,GAAG,EAA4B,CAAC;AACnD,MAAM,YAAY,GAAG,IAAI,GAAG,EAA8B,CAAC;AAE3D,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,iBAAiB,CAAC,KAAyB;IACzD,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,OAAO,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AACnC,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 registerHowToCollectStats(server: McpServer): void;
3
+ //# sourceMappingURL=how-to-collect-stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"how-to-collect-stats.d.ts","sourceRoot":"","sources":["../../src/tools/how-to-collect-stats.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA4HpE,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA8BjE"}
@@ -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,CA+ChE"}
@@ -0,0 +1,49 @@
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. Required before trace_import.',
10
+ inputSchema: {
11
+ buildId: z.string().describe('Build ID returned by load_build_stats; the stats.json is read from that build directory'),
12
+ },
13
+ }, async ({ buildId }) => {
14
+ const build = getBuildStats(buildId);
15
+ if (!build) {
16
+ throw new Error(`Build "${buildId}" not found. Call load_build_stats first and pass the buildId it returns.`);
17
+ }
18
+ const stats = await parseWebpackStats(build.buildDir, buildId);
19
+ if (!stats) {
20
+ return statsTextResult({
21
+ buildId,
22
+ webpackStatsLoaded: false,
23
+ message: `No stats.json found in ${build.buildDir}. A stock next build does not emit one.`,
24
+ nextStep: 'Call how_to_collect_stats to generate .next/stats.json, then load_webpack_stats again.',
25
+ });
26
+ }
27
+ storeWebpackStats(stats);
28
+ const looksCollapsed = stats.parsedModuleCount === 0 || stats.chunks.length === 0;
29
+ return statsTextResult({
30
+ buildId,
31
+ webpackStatsLoaded: true,
32
+ statsPath: stats.statsPath,
33
+ moduleCount: stats.moduleCount,
34
+ parsedModuleCount: stats.parsedModuleCount,
35
+ chunkCount: stats.chunks.length,
36
+ ...(looksCollapsed
37
+ ? {
38
+ warning: 'The stats file parsed but contains no usable modules/chunks. This usually means the stats ' +
39
+ 'config collapsed the module graph (webpack groups modules once `modulesSpace` is exceeded and ' +
40
+ 'omits chunk ids unless `ids: true`). Re-run how_to_collect_stats for the corrected config and rebuild.',
41
+ }
42
+ : {}),
43
+ nextStep: looksCollapsed
44
+ ? 'Call how_to_collect_stats again, apply the corrected stats config, rebuild, then load_webpack_stats.'
45
+ : 'Now call trace_import with a module or package name (e.g. a heavy dependency) to see why it is bundled.',
46
+ });
47
+ });
48
+ }
49
+ //# 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,iDAAiD;QACnD,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,yGAAyG;SAC9G,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.3.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",