Skip to content

Commit b9a132e

Browse files
committed
[MCP] Add manual compile mode to pause Turbopack scheduling during batched edits
1 parent b6017fa commit b9a132e

File tree

14 files changed

+339
-0
lines changed

14 files changed

+339
-0
lines changed

crates/next-napi-bindings/src/next_api/project.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2134,6 +2134,31 @@ pub fn project_update_info_subscribe(
21342134
Ok(())
21352135
}
21362136

2137+
/// Pause compilation. Task scheduling is deferred — file changes still mark
2138+
/// tasks dirty, but no recomputation happens until `project_compile_and_resume`
2139+
/// is called.
2140+
#[napi]
2141+
pub fn project_pause_compilation(
2142+
#[napi(ts_arg_type = "{ __napiType: \"Project\" }")] project: External<ProjectInstance>,
2143+
) -> napi::Result<()> {
2144+
project.turbopack_ctx.turbo_tasks().pause_compilation();
2145+
Ok(())
2146+
}
2147+
2148+
/// Compile all deferred tasks in one batch, wait for the compilation cycle to
2149+
/// finish, then resume normal scheduling.
2150+
#[napi]
2151+
pub async fn project_compile_and_resume(
2152+
#[napi(ts_arg_type = "{ __napiType: \"Project\" }")] project: External<ProjectInstance>,
2153+
) -> napi::Result<()> {
2154+
project
2155+
.turbopack_ctx
2156+
.turbo_tasks()
2157+
.compile_and_resume()
2158+
.await;
2159+
Ok(())
2160+
}
2161+
21372162
/// Subscribes to all compilation events that are not cached like timing and progress information.
21382163
#[napi]
21392164
pub fn project_compilation_events_subscribe(

packages/next/src/build/swc/generated-native.d.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,21 @@ export declare function projectUpdate(
344344
export declare function projectInvalidateFileSystemCache(project: {
345345
__napiType: 'Project'
346346
}): Promise<void>
347+
/**
348+
* Pause compilation. Task scheduling is deferred — file changes still mark
349+
* tasks dirty, but no recomputation happens until `projectCompileAndResume`
350+
* is called.
351+
*/
352+
export declare function projectPauseCompilation(project: {
353+
__napiType: 'Project'
354+
}): void
355+
/**
356+
* Compile all deferred tasks in one batch, wait for the compilation cycle to
357+
* finish, then resume normal scheduling.
358+
*/
359+
export declare function projectCompileAndResume(project: {
360+
__napiType: 'Project'
361+
}): Promise<void>
347362
/**
348363
* Runs exit handlers for the project registered using the [`ExitHandler`] API.
349364
*

packages/next/src/build/swc/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,14 @@ function bindingToApi(
843843
return binding.projectInvalidateFileSystemCache(this._nativeProject)
844844
}
845845

846+
pauseCompilation(): void {
847+
binding.projectPauseCompilation(this._nativeProject)
848+
}
849+
850+
compileAndResume(): Promise<void> {
851+
return binding.projectCompileAndResume(this._nativeProject)
852+
}
853+
846854
shutdown(): Promise<void> {
847855
return binding.projectShutdown(this._nativeProject)
848856
}

packages/next/src/build/swc/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,10 @@ export interface Project {
333333

334334
invalidateFileSystemCache(): Promise<void>
335335

336+
pauseCompilation(): void
337+
338+
compileAndResume(): Promise<void>
339+
336340
shutdown(): Promise<void>
337341

338342
onExit(): Promise<void>

packages/next/src/server/dev/hot-reloader-turbopack.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,6 +1050,12 @@ export async function createHotReloaderTurbopack(
10501050
clientsWithoutHtmlRequestId.size + clientsByHtmlRequestId.size,
10511051
getDevServerUrl: () => process.env.__NEXT_PRIVATE_ORIGIN,
10521052
getTurbopackProject: () => project,
1053+
pauseCompilation: () => {
1054+
project.pauseCompilation()
1055+
},
1056+
compileAndResume: async () => {
1057+
await project.compileAndResume()
1058+
},
10531059
}),
10541060
]
10551061
: []),

packages/next/src/server/dev/hot-reloader-webpack.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1699,6 +1699,8 @@ export default class HotReloaderWebpack implements NextJsHotReloaderInterface {
16991699
getActiveConnectionCount: () =>
17001700
this.webpackHotMiddleware?.getClientCount() ?? 0,
17011701
getDevServerUrl: () => process.env.__NEXT_PRIVATE_ORIGIN,
1702+
pauseCompilation: () => {},
1703+
compileAndResume: async () => {},
17021704
}),
17031705
]
17041706
: [])

packages/next/src/server/mcp/get-or-create-mcp-server.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import { registerGetLogsTool } from './tools/get-logs'
66
import { registerGetActionByIdTool } from './tools/get-server-action-by-id'
77
import { registerGetRoutesTool } from './tools/get-routes'
88
import { registerGetCompilationIssuesTool } from './tools/get-compilation-issues'
9+
import {
10+
registerPauseCompilationTool,
11+
registerCompileAndResumeTool,
12+
} from './tools/manual-compile'
913
import type { HmrMessageSentToBrowser } from '../dev/hot-reloader-types'
1014
import type { NextConfigComplete } from '../config-shared'
1115
import type { Project } from '../../build/swc/types'
@@ -20,6 +24,8 @@ export interface McpServerOptions {
2024
getActiveConnectionCount: () => number
2125
getDevServerUrl: () => string | undefined
2226
getTurbopackProject?: () => Project | undefined
27+
pauseCompilation: () => void
28+
compileAndResume: () => Promise<void>
2329
}
2430

2531
let mcpServer: McpServer | undefined
@@ -62,5 +68,8 @@ export const getOrCreateMcpServer = (options: McpServerOptions) => {
6268
registerGetCompilationIssuesTool(mcpServer, options.getTurbopackProject)
6369
}
6470

71+
registerPauseCompilationTool(mcpServer, options.pauseCompilation)
72+
registerCompileAndResumeTool(mcpServer, options.compileAndResume)
73+
6574
return mcpServer
6675
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import type { McpServer } from 'next/dist/compiled/@modelcontextprotocol/sdk/server/mcp'
2+
import { mcpTelemetryTracker } from '../mcp-telemetry-tracker'
3+
4+
export function registerPauseCompilationTool(
5+
server: McpServer,
6+
pauseCompilation: () => void
7+
) {
8+
server.registerTool(
9+
'pause_compilation',
10+
{
11+
description:
12+
'Pause compilation. File watching continues, but compilation is deferred until compile_and_resume is called. Use this at the start of an editing session to prevent wasted intermediate compilations and ephemeral errors.',
13+
inputSchema: {},
14+
},
15+
async () => {
16+
mcpTelemetryTracker.recordToolCall('mcp/pause_compilation')
17+
pauseCompilation()
18+
return {
19+
content: [
20+
{
21+
type: 'text',
22+
text: JSON.stringify({ status: 'compilation_paused' }),
23+
},
24+
],
25+
}
26+
}
27+
)
28+
}
29+
30+
export function registerCompileAndResumeTool(
31+
server: McpServer,
32+
compileAndResume: () => Promise<void>
33+
) {
34+
server.registerTool(
35+
'compile_and_resume',
36+
{
37+
description:
38+
'Compile all accumulated file changes in a single batch, then resume normal compilation. Waits for compilation to complete before returning. No-op if compilation is not paused.',
39+
inputSchema: {},
40+
},
41+
async () => {
42+
mcpTelemetryTracker.recordToolCall('mcp/compile_and_resume')
43+
await compileAndResume()
44+
return {
45+
content: [
46+
{
47+
type: 'text',
48+
text: JSON.stringify({ status: 'compiled_and_resumed' }),
49+
},
50+
],
51+
}
52+
}
53+
)
54+
}

packages/next/src/telemetry/events/build.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,8 @@ export type McpToolName =
260260
| 'mcp/get_routes'
261261
| 'mcp/get_server_action_by_id'
262262
| 'mcp/get_compilation_issues'
263+
| 'mcp/pause_compilation'
264+
| 'mcp/compile_and_resume'
263265

264266
export type EventMcpToolUsage = {
265267
toolName: McpToolName
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export default function RootLayout({
2+
children,
3+
}: {
4+
children: React.ReactNode
5+
}) {
6+
return (
7+
<html lang="en">
8+
<body>{children}</body>
9+
</html>
10+
)
11+
}

0 commit comments

Comments
 (0)