Skip to content

Add MCP compile_route tool#93337

Open
lukesandberg wants to merge 3 commits intoeviction_semantics_for_real_this_timefrom
compile_route_mcp_handler
Open

Add MCP compile_route tool#93337
lukesandberg wants to merge 3 commits intoeviction_semantics_for_real_this_timefrom
compile_route_mcp_handler

Conversation

@lukesandberg
Copy link
Copy Markdown
Contributor

@lukesandberg lukesandberg commented Apr 29, 2026

What?

Adds a compile_route MCP tool that triggers on-demand compilation of a specific route (app or pages) without issuing an HTTP request, and returns any compilation issues.

Why?

Coding agents and benchmarking workflows need a way to warm the module graph or measure compile time for a route without standing up a live backend to satisfy the request. The existing path — hitting the URL — requires the route's runtime dependencies to be available and couples compile timing to request handling.

How?

  • New tool mcp/compile_route registered in get-or-create-mcp-server.ts, backed by a compileRoute({ page, clientOnly }) callback plumbed from the Turbopack hot reloader
  • Reuses the dev server's existing on-demand entry path (ensurePage / handleRouteType), so the call path matches a first navigation.
  • Adds a subscribeToChanges opt-out on ensurePage and threads it through handleRouteType / handlePagesErrorRoute. One-shot MCP compilations skip HMR subscription wiring — without this, each call would leak a subscription that fires on every subsequent file change for the life of the dev server.
  • Telemetry: registers mcp/compile_route in the McpToolName union.
  • e2e test in test/development/mcp-server/mcp-server-compile-route.test.ts.

Copy link
Copy Markdown
Contributor Author

lukesandberg commented Apr 29, 2026

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@github-actions github-actions Bot added created-by: Turbopack team PRs by the Turbopack team. Documentation Related to Next.js' official documentation. tests type: next labels Apr 29, 2026
@lukesandberg lukesandberg changed the title compile route mcsp handler Compile route mcp tool Apr 29, 2026
@lukesandberg lukesandberg changed the title Compile route mcp tool Add MCP compile_route tool Apr 29, 2026
@lukesandberg lukesandberg marked this pull request as ready for review April 29, 2026 00:14
@lukesandberg lukesandberg requested a review from gaojude April 29, 2026 00:14
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 29, 2026

Failing test suites

Commit: 1d1d1b7 | About building and testing Next.js

pnpm test-dev test/e2e/filesystem-cache/evict-after-snapshot.test.ts (job)

  • evict-after-snapshot > should serve correct content after eviction and HMR (DD)
  • evict-after-snapshot > should handle client component HMR after eviction (DD)
Expand output

● evict-after-snapshot › should serve correct content after eviction and HMR

expect(received).toBe(expected) // Object.is equality

Expected: "cycle 2"
Received: "cycle 1"

  55 |         const expected = currentContent
  56 |         await retry(async () => {
> 57 |           expect(await browser.elementByCss('p').text()).toBe(expected)
     |                                                          ^
  58 |         }, 10000)
  59 |       }
  60 |

  at toBe (e2e/filesystem-cache/evict-after-snapshot.test.ts:57:58)
  at retry (lib/next-test-utils.ts:862:14)
  at Object.<anonymous> (e2e/filesystem-cache/evict-after-snapshot.test.ts:56:9)

● evict-after-snapshot › should handle client component HMR after eviction

expect(received).toBe(expected) // Object.is equality

Expected: "hello eviction"
Received: "hello world"

  78 |         async () => {
  79 |           await retry(async () => {
> 80 |             expect(await browser.elementByCss('p').text()).toBe(
     |                                                            ^
  81 |               'hello eviction'
  82 |             )
  83 |           }, 10000)

  at toBe (e2e/filesystem-cache/evict-after-snapshot.test.ts:80:60)
  at retry (lib/next-test-utils.ts:862:14)
  at e2e/filesystem-cache/evict-after-snapshot.test.ts:79:11
  at NextDevInstance.patchFile (lib/next-modes/base.ts:756:9)
  at NextDevInstance.patchFile (lib/next-modes/next-dev.ts:304:16)
  at Object.<anonymous> (e2e/filesystem-cache/evict-after-snapshot.test.ts:75:7)

pnpm test-start test/e2e/app-dir/next-after-app-static/build-time/build-time.test.ts (job)

  • after() in static pages > runs after during build (DD)
Expand output

● after() in static pages › runs after during build

can not run export while server is running, use next.stop() first

  251 |   ) {
  252 |     if (this.childProcess) {
> 253 |       throw new Error(
      |             ^
  254 |         `can not run export while server is running, use next.stop() first`
  255 |       )
  256 |     }

  at NextStartInstance.build (lib/next-modes/next-start.ts:253:13)
  at Object.build (e2e/app-dir/next-after-app-static/build-time/build-time.test.ts:36:36)

pnpm test-dev-turbo test/e2e/filesystem-cache/evict-after-snapshot.test.ts (turbopack) (job)

  • evict-after-snapshot > should serve correct content after eviction and HMR (DD)
  • evict-after-snapshot > should handle client component HMR after eviction (DD)
Expand output

● evict-after-snapshot › should serve correct content after eviction and HMR

expect(received).toBe(expected) // Object.is equality

Expected: "cycle 2"
Received: "cycle 1"

  55 |         const expected = currentContent
  56 |         await retry(async () => {
> 57 |           expect(await browser.elementByCss('p').text()).toBe(expected)
     |                                                          ^
  58 |         }, 10000)
  59 |       }
  60 |

  at toBe (e2e/filesystem-cache/evict-after-snapshot.test.ts:57:58)
  at retry (lib/next-test-utils.ts:862:14)
  at Object.<anonymous> (e2e/filesystem-cache/evict-after-snapshot.test.ts:56:9)

● evict-after-snapshot › should handle client component HMR after eviction

expect(received).toBe(expected) // Object.is equality

Expected: "hello eviction"
Received: "hello world"

  78 |         async () => {
  79 |           await retry(async () => {
> 80 |             expect(await browser.elementByCss('p').text()).toBe(
     |                                                            ^
  81 |               'hello eviction'
  82 |             )
  83 |           }, 10000)

  at toBe (e2e/filesystem-cache/evict-after-snapshot.test.ts:80:60)
  at retry (lib/next-test-utils.ts:862:14)
  at e2e/filesystem-cache/evict-after-snapshot.test.ts:79:11
  at NextDevInstance.patchFile (lib/next-modes/base.ts:756:9)
  at NextDevInstance.patchFile (lib/next-modes/next-dev.ts:304:16)
  at Object.<anonymous> (e2e/filesystem-cache/evict-after-snapshot.test.ts:75:7)

@lukesandberg lukesandberg force-pushed the compile_route_mcp_handler branch from c21addb to 93f92ea Compare April 29, 2026 01:25
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 29, 2026

Stats from current PR

🔴 3 regressions

Metric Canary PR Change Trend
node_modules Size 494 MB 494 MB 🔴 +133 kB (+0%) █████
Webpack Build Time 23.051s 23.592s 🔴 +541ms (+2%) ▂▅▁▄▃
Webpack Build Time (cached) 22.863s 23.607s 🔴 +744ms (+3%) ▂▆▁█▅
📊 All Metrics
📖 Metrics Glossary

Dev Server Metrics:

  • Listen = TCP port starts accepting connections
  • First Request = HTTP server returns successful response
  • Cold = Fresh build (no cache)
  • Warm = With cached build artifacts

Build Metrics:

  • Fresh = Clean build (no .next directory)
  • Cached = With existing .next directory

Change Thresholds:

  • Time: Changes < 50ms AND < 10%, OR < 2% are insignificant
  • Size: Changes < 1KB AND < 1% are insignificant
  • All other changes are flagged to catch regressions

⚡ Dev Server

Metric Canary PR Change Trend
Cold (Listen) 710ms 710ms ▇▃▁▇▅
Cold (Ready in log) 710ms 707ms ▄▂▂▅▃
Cold (First Request) 1.199s 1.202s ▆▂▂▄▃
Warm (Listen) 710ms 710ms ▁▁▁▁▁
Warm (Ready in log) 709ms 710ms ▄▂▂▄▃
Warm (First Request) 565ms 565ms ▇▃▃▇▅
📦 Dev Server (Webpack) (Legacy)

📦 Dev Server (Webpack)

Metric Canary PR Change Trend
Cold (Listen) 761ms 761ms ▁█▃█▃
Cold (Ready in log) 747ms 730ms ▆▁▁▄▆
Cold (First Request) 3.164s 3.095s ▆▂▄▅▅
Warm (Listen) 762ms 760ms ▁▃▃█▆
Warm (Ready in log) 741ms 735ms ▅▃▁▅▆
Warm (First Request) 3.140s 3.100s ▆▁▃█▅

⚡ Production Builds

Metric Canary PR Change Trend
Fresh Build 4.768s 4.758s ▆▁▂▂▄
Cached Build 4.757s 4.752s ▆▁▁▃▂
📦 Production Builds (Webpack) (Legacy)

📦 Production Builds (Webpack)

Metric Canary PR Change Trend
Fresh Build 23.051s 23.592s 🔴 +541ms (+2%) ▂▅▁▄▃
Cached Build 22.863s 23.607s 🔴 +744ms (+3%) ▂▆▁█▅
node_modules Size 494 MB 494 MB 🔴 +133 kB (+0%) █████
📦 Bundle Sizes

Bundle Sizes

⚡ Turbopack

Client

Main Bundles
Canary PR Change
0-kpcfk34v387.js gzip 49.5 kB N/A -
04f_yppvzjwz4.js gzip 8.56 kB N/A -
0c3t8_jjpep4b.js gzip 10.1 kB N/A -
0cz1d0mv5g_q7.js gzip 39.4 kB 39.4 kB
0eoi7fyufnxxy.js gzip 10.4 kB N/A -
0fli3_wppnim5.js gzip 12.9 kB N/A -
0pwy28qdxiamv.js gzip 9.82 kB N/A -
0qqq1yaqhw4ab.js gzip 159 B N/A -
0t0o81rw_yp50.js gzip 8.51 kB N/A -
0taw4eih9vjsu.js gzip 160 B N/A -
0vwsvp4fi5fl_.js gzip 156 B N/A -
0wxpyd8r-vipl.js gzip 1.47 kB N/A -
0zxf3wtv-b9n2.js gzip 153 B N/A -
10nsskx6qxplf.js gzip 5.74 kB N/A -
16lhqjoqbznyg.js gzip 220 B 220 B
1cgcsmbb9p2a-.js gzip 8.59 kB N/A -
1elt1qium-r2m.css gzip 115 B 115 B
1fzhh47xue5c_.js gzip 156 B N/A -
1q4j6c94t8npp.js gzip 8.62 kB N/A -
1x6sk2-0xj2wh.js gzip 155 B N/A -
2__-e_ym8n788.js gzip 450 B N/A -
22o6xd9_ywdu6.js gzip 233 B N/A -
271-grvwep_j1.js gzip 157 B N/A -
2f71ojcamzra_.js gzip 7.61 kB N/A -
2faj3acmavn9n.js gzip 13.1 kB N/A -
2fow1yah7yjd1.js gzip 65.6 kB N/A -
2g430q8a-_b5t.js gzip 8.51 kB N/A -
2p30m1zmxtah9.js gzip 156 B N/A -
2s3k_b9acwpqh.js gzip 8.6 kB N/A -
3011hythgerqs.js gzip 156 B N/A -
342ijzvrpe53h.js gzip 2.29 kB N/A -
36v24g62pdqy6.js gzip 13.4 kB N/A -
3f641_wrg14mf.js gzip 8.56 kB N/A -
3k6mr1etvjc6e.js gzip 153 B N/A -
3l3_x3vwnanol.js gzip 156 B N/A -
3mj722uvk8dnb.js gzip 153 B N/A -
3u9hyuc0c-9bm.js gzip 9.24 kB N/A -
3yc5jsuzmy7x7.js gzip 168 B N/A -
40mrbpnj40pxt.js gzip 8.58 kB N/A -
40q0bc2emxqsh.js gzip 70.8 kB N/A -
42_02jza_7yny.js gzip 13.8 kB N/A -
turbopack-03..kfgw.js gzip 4.2 kB N/A -
turbopack-0c..qklo.js gzip 4.21 kB N/A -
turbopack-0g..rtwr.js gzip 4.19 kB N/A -
turbopack-19..4lxb.js gzip 4.18 kB N/A -
turbopack-1g..4y71.js gzip 4.2 kB N/A -
turbopack-1i..hs4b.js gzip 4.2 kB N/A -
turbopack-2_..lp3v.js gzip 4.2 kB N/A -
turbopack-23..7fxn.js gzip 4.2 kB N/A -
turbopack-2a..0k1i.js gzip 4.2 kB N/A -
turbopack-2i..s0lh.js gzip 4.2 kB N/A -
turbopack-2z..9j6v.js gzip 4.2 kB N/A -
turbopack-3o..5pql.js gzip 4.19 kB N/A -
turbopack-3z..s39f.js gzip 4.2 kB N/A -
turbopack-43..0h_4.js gzip 4.2 kB N/A -
0_4irlhf-eu2n.js gzip N/A 7.61 kB -
0_nd65ko8cci4.js gzip N/A 156 B -
0eijy0qw1gk23.js gzip N/A 8.6 kB -
0et8k1y0i8o1r.js gzip N/A 49.5 kB -
0f1dxxdv7lu96.js gzip N/A 159 B -
0sbj2lujahws6.js gzip N/A 157 B -
0wh9j68i3dfq3.js gzip N/A 8.56 kB -
0xqhfcdi7u9f9.js gzip N/A 10.1 kB -
1-jqyfc89tixo.js gzip N/A 1.46 kB -
11vvay5vgcahf.js gzip N/A 157 B -
14t1kneseb8th.js gzip N/A 2.3 kB -
16dveru-o4vft.js gzip N/A 8.58 kB -
19cp0pggb09-4.js gzip N/A 13.4 kB -
1ab2xruymo-oj.js gzip N/A 449 B -
1b82lh0rann9c.js gzip N/A 8.52 kB -
1dt49_v4y8lxb.js gzip N/A 13.8 kB -
1jfmf06ccqtpj.js gzip N/A 157 B -
1rja0tc5kflqo.js gzip N/A 8.6 kB -
1v9rhhb9xjp2o.js gzip N/A 159 B -
1vmibvuhp1gey.js gzip N/A 13.1 kB -
24vv7v9-5pgdr.js gzip N/A 70.8 kB -
25qfzj840qqn1.js gzip N/A 8.56 kB -
2dx965o_31xgs.js gzip N/A 168 B -
2u_rpxq3tzytl.js gzip N/A 233 B -
2zy2k0ys7jgl1.js gzip N/A 10.4 kB -
31rkfai0jcyg5.js gzip N/A 154 B -
368lim5wq0o0r.js gzip N/A 12.9 kB -
3druob2gm1rqz.js gzip N/A 5.74 kB -
3f7gairhv7pqd.js gzip N/A 158 B -
3i42rzutvh_ut.js gzip N/A 163 B -
3nf7p0wqhetky.js gzip N/A 8.51 kB -
3r__eofa1ec-9.js gzip N/A 158 B -
3sa5hpibp7c73.js gzip N/A 162 B -
3su527zkc99te.js gzip N/A 9.24 kB -
3uew-vlymwsya.js gzip N/A 158 B -
42w19o6nrnj6x.js gzip N/A 65.5 kB -
43kv63t-db7vv.js gzip N/A 8.63 kB -
43x51bgxirzyn.js gzip N/A 9.82 kB -
turbopack-0a..cw-z.js gzip N/A 4.2 kB -
turbopack-11..1szn.js gzip N/A 4.2 kB -
turbopack-22..v_q9.js gzip N/A 4.2 kB -
turbopack-27..ep4k.js gzip N/A 4.18 kB -
turbopack-2q..bmc6.js gzip N/A 4.2 kB -
turbopack-3f.._nqn.js gzip N/A 4.2 kB -
turbopack-3f..w7aj.js gzip N/A 4.2 kB -
turbopack-3o..6xrl.js gzip N/A 4.2 kB -
turbopack-3o..anld.js gzip N/A 4.21 kB -
turbopack-3w..x9n-.js gzip N/A 4.2 kB -
turbopack-3y..knrb.js gzip N/A 4.2 kB -
turbopack-3z..l7jg.js gzip N/A 4.2 kB -
turbopack-3z..whno.js gzip N/A 4.2 kB -
turbopack-41..0yzt.js gzip N/A 4.2 kB -
Total 466 kB 466 kB ⚠️ +99 B

Server

Middleware
Canary PR Change
middleware-b..fest.js gzip 725 B 715 B 🟢 10 B (-1%)
Total 725 B 715 B ✅ -10 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 435 B 428 B 🟢 7 B (-2%)
Total 435 B 428 B ✅ -7 B

📦 Webpack

Client

Main Bundles
Canary PR Change
2637-HASH.js gzip 4.68 kB N/A -
7724.HASH.js gzip 169 B N/A -
8274-HASH.js gzip 61.4 kB N/A -
8817-HASH.js gzip 5.63 kB N/A -
c3500254-HASH.js gzip 62.7 kB N/A -
framework-HASH.js gzip 59.7 kB 59.7 kB
main-app-HASH.js gzip 254 B 255 B
main-HASH.js gzip 39.4 kB 39.4 kB
webpack-HASH.js gzip 1.68 kB 1.68 kB
5887-HASH.js gzip N/A 5.65 kB -
6522-HASH.js gzip N/A 60.8 kB -
6779-HASH.js gzip N/A 4.67 kB -
8854.HASH.js gzip N/A 169 B -
eab920f9-HASH.js gzip N/A 62.7 kB -
Total 236 kB 235 kB ✅ -632 B
Polyfills
Canary PR Change
polyfills-HASH.js gzip 39.4 kB 39.4 kB
Total 39.4 kB 39.4 kB
Pages
Canary PR Change
_app-HASH.js gzip 193 B 193 B
_error-HASH.js gzip 182 B 182 B
css-HASH.js gzip 333 B 334 B
dynamic-HASH.js gzip 1.81 kB 1.8 kB
edge-ssr-HASH.js gzip 255 B 255 B
head-HASH.js gzip 353 B 349 B 🟢 4 B (-1%)
hooks-HASH.js gzip 384 B 382 B
image-HASH.js gzip 581 B 581 B
index-HASH.js gzip 260 B 259 B
link-HASH.js gzip 2.52 kB 2.52 kB
routerDirect..HASH.js gzip 316 B 318 B
script-HASH.js gzip 386 B 386 B
withRouter-HASH.js gzip 313 B 314 B
1afbb74e6ecf..834.css gzip 106 B 106 B
Total 7.99 kB 7.98 kB ✅ -10 B

Server

Edge SSR
Canary PR Change
edge-ssr.js gzip 126 kB 126 kB
page.js gzip 274 kB 274 kB
Total 400 kB 399 kB ✅ -522 B
Middleware
Canary PR Change
middleware-b..fest.js gzip 618 B 616 B
middleware-r..fest.js gzip 156 B 156 B
middleware.js gzip 44.2 kB 44.5 kB
edge-runtime..pack.js gzip 842 B 842 B
Total 45.8 kB 46.1 kB ⚠️ +283 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 722 B 719 B
Total 722 B 719 B ✅ -3 B
Build Cache
Canary PR Change
0.pack gzip 4.44 MB 4.43 MB
index.pack gzip 116 kB 115 kB
index.pack.old gzip 115 kB 115 kB
Total 4.67 MB 4.66 MB ✅ -3.11 kB

🔄 Shared (bundler-independent)

Runtimes
Canary PR Change
app-page-exp...dev.js gzip 349 kB 349 kB
app-page-exp..prod.js gzip 193 kB 193 kB
app-page-tur...dev.js gzip 348 kB 348 kB
app-page-tur..prod.js gzip 193 kB 193 kB
app-page-tur...dev.js gzip 345 kB 345 kB
app-page-tur..prod.js gzip 191 kB 191 kB
app-page.run...dev.js gzip 345 kB 345 kB
app-page.run..prod.js gzip 191 kB 191 kB
app-route-ex...dev.js gzip 77.4 kB 77.4 kB
app-route-ex..prod.js gzip 52.8 kB 52.8 kB
app-route-tu...dev.js gzip 77.4 kB 77.4 kB
app-route-tu..prod.js gzip 52.8 kB 52.8 kB
app-route-tu...dev.js gzip 77 kB 77 kB
app-route-tu..prod.js gzip 52.6 kB 52.6 kB
app-route.ru...dev.js gzip 77 kB 77 kB
app-route.ru..prod.js gzip 52.6 kB 52.6 kB
dist_client_...dev.js gzip 324 B 324 B
dist_client_...dev.js gzip 326 B 326 B
dist_client_...dev.js gzip 318 B 318 B
dist_client_...dev.js gzip 317 B 317 B
pages-api-tu...dev.js gzip 44.2 kB 44.2 kB
pages-api-tu..prod.js gzip 33.7 kB 33.7 kB
pages-api.ru...dev.js gzip 44.2 kB 44.2 kB
pages-api.ru..prod.js gzip 33.7 kB 33.7 kB
pages-turbo....dev.js gzip 53.7 kB 53.7 kB
pages-turbo...prod.js gzip 39.4 kB 39.4 kB
pages.runtim...dev.js gzip 53.7 kB 53.7 kB
pages.runtim..prod.js gzip 39.4 kB 39.4 kB
server.runti..prod.js gzip 63.1 kB 63.1 kB
Total 3.08 MB 3.08 MB ⚠️ +1 B
📎 Tarball URL
https://vercel-packages.vercel.app/next/commits/4901a27ee68ce521927734372d5cf3b46f2496e9/next

Commit: 4901a27

@lukesandberg lukesandberg force-pushed the eviction_semantics_for_real_this_time branch from 1e85dc1 to 7a4059f Compare April 30, 2026 22:54
@lukesandberg lukesandberg force-pushed the compile_route_mcp_handler branch 2 times, most recently from 9f379f5 to 18e3b1d Compare May 1, 2026 18:52
@lukesandberg lukesandberg force-pushed the eviction_semantics_for_real_this_time branch from 7a4059f to 1f9c55d Compare May 1, 2026 18:52
@lukesandberg lukesandberg force-pushed the compile_route_mcp_handler branch from 18e3b1d to 8261a35 Compare May 1, 2026 22:44
@lukesandberg lukesandberg force-pushed the eviction_semantics_for_real_this_time branch from 1f9c55d to 5a1ac64 Compare May 1, 2026 22:44
@lukesandberg lukesandberg force-pushed the compile_route_mcp_handler branch from 8261a35 to 3f1bb5d Compare May 1, 2026 23:08
@lukesandberg lukesandberg force-pushed the eviction_semantics_for_real_this_time branch 2 times, most recently from 07114f2 to c5e00ef Compare May 2, 2026 05:43
@lukesandberg lukesandberg force-pushed the compile_route_mcp_handler branch 2 times, most recently from d31c1a1 to e684a7c Compare May 4, 2026 20:08
@lukesandberg lukesandberg force-pushed the eviction_semantics_for_real_this_time branch from 327ffb1 to 94bd15c Compare May 4, 2026 22:42
@lukesandberg lukesandberg force-pushed the compile_route_mcp_handler branch 2 times, most recently from 1d1d1b7 to 4901a27 Compare May 5, 2026 16:43
@lukesandberg lukesandberg force-pushed the compile_route_mcp_handler branch from 4901a27 to d805ca2 Compare May 5, 2026 23:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

created-by: Turbopack team PRs by the Turbopack team. Documentation Related to Next.js' official documentation. tests type: next

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant