@@ -130,6 +130,28 @@ const CLIENT_CONFIG_TEMPLATE = `// VS Code DevTools MCP — Client Configuration
130130}
131131` ;
132132
133+ /**
134+ * Hot-reload configuration for automatic change detection and rebuild.
135+ * All fields are optional with sensible defaults.
136+ */
137+ export interface HotReloadConfig {
138+ /** Master switch — set false to disable all hot-reload checks. Default: true */
139+ enabled : boolean ;
140+ /** The MCP server name as defined in .vscode/mcp.json. Used for VS Code server definition ID. Default: 'vscode-devtools' */
141+ mcpServerName : string ;
142+ /** Delay (ms) between stopping and starting MCP server during restart. Default: 2000 */
143+ restartDelay : number ;
144+ /** Max wait time (ms) for mcpStatus LM tool before timeout. Default: 60000 */
145+ mcpStatusTimeout : number ;
146+ }
147+
148+ const DEFAULT_HOT_RELOAD_CONFIG : HotReloadConfig = {
149+ enabled : true ,
150+ mcpServerName : 'vscode-devtools' ,
151+ restartDelay : 2000 ,
152+ mcpStatusTimeout : 60_000 ,
153+ } ;
154+
133155/**
134156 * Host configuration read from .devtools/host.config.jsonc.
135157 * Controls which client workspace and extension to use.
@@ -139,6 +161,8 @@ export interface HostConfig {
139161 clientWorkspace ?: string ;
140162 /** Path to the extension folder (absolute or relative to host root). If omitted, no extension is loaded. */
141163 extensionPath ?: string ;
164+ /** Hot-reload configuration. All fields optional with sensible defaults. */
165+ hotReload ?: Partial < HotReloadConfig > ;
142166}
143167
144168/**
@@ -179,6 +203,8 @@ export interface ResolvedConfig {
179203 experimentalVision : boolean ;
180204 experimentalStructuredContent : boolean ;
181205 launch : LaunchFlags ;
206+ /** Resolved hot-reload configuration with defaults applied */
207+ hotReload : HotReloadConfig ;
182208}
183209
184210function isRecord ( value : unknown ) : value is Record < string , unknown > {
@@ -201,6 +227,14 @@ function readOptionalBoolean(
201227 return typeof value === 'boolean' ? value : undefined ;
202228}
203229
230+ function readOptionalNumber (
231+ obj : Record < string , unknown > ,
232+ key : string ,
233+ ) : number | undefined {
234+ const value = obj [ key ] ;
235+ return typeof value === 'number' && Number . isFinite ( value ) ? value : undefined ;
236+ }
237+
204238function readOptionalStringArray (
205239 obj : Record < string , unknown > ,
206240 key : string ,
@@ -226,6 +260,25 @@ function coerceHostConfig(value: unknown): HostConfig {
226260 const extensionPath = readOptionalString ( value , 'extensionPath' ) ;
227261 if ( extensionPath ) { config . extensionPath = extensionPath ; }
228262
263+ const hotReloadValue = value [ 'hotReload' ] ;
264+ if ( isRecord ( hotReloadValue ) ) {
265+ const hotReload : Partial < HotReloadConfig > = { } ;
266+
267+ const enabled = readOptionalBoolean ( hotReloadValue , 'enabled' ) ;
268+ if ( typeof enabled === 'boolean' ) { hotReload . enabled = enabled ; }
269+
270+ const mcpServerName = readOptionalString ( hotReloadValue , 'mcpServerName' ) ;
271+ if ( mcpServerName ) { hotReload . mcpServerName = mcpServerName ; }
272+
273+ const restartDelay = readOptionalNumber ( hotReloadValue , 'restartDelay' ) ;
274+ if ( typeof restartDelay === 'number' ) { hotReload . restartDelay = restartDelay ; }
275+
276+ const mcpStatusTimeout = readOptionalNumber ( hotReloadValue , 'mcpStatusTimeout' ) ;
277+ if ( typeof mcpStatusTimeout === 'number' ) { hotReload . mcpStatusTimeout = mcpStatusTimeout ; }
278+
279+ config . hotReload = hotReload ;
280+ }
281+
229282 return config ;
230283}
231284
@@ -364,6 +417,15 @@ function resolveLaunchFlags(partial?: Partial<LaunchFlags>): LaunchFlags {
364417 } ;
365418}
366419
420+ /** Merge partial hot-reload config over defaults. */
421+ function resolveHotReloadConfig ( partial ?: Partial < HotReloadConfig > ) : HotReloadConfig {
422+ if ( ! partial ) { return { ...DEFAULT_HOT_RELOAD_CONFIG } ; }
423+ return {
424+ ...DEFAULT_HOT_RELOAD_CONFIG ,
425+ ...partial ,
426+ } ;
427+ }
428+
367429/**
368430 * Resolve a path relative to workspace folder, or return absolute path as-is
369431 */
@@ -378,6 +440,16 @@ export function getHostWorkspace(): string {
378440 return dirname ( packageRoot ) ;
379441}
380442
443+ /**
444+ * Get the MCP server package root directory.
445+ * Build output is at mcp-server/build/src/, so __dirname goes up twice.
446+ *
447+ * Relocated from mcp-server-watcher.ts to consolidate path utilities.
448+ */
449+ export function getMcpServerRoot ( ) : string {
450+ return dirname ( dirname ( __dirname ) ) ;
451+ }
452+
381453// Module-level storage for the resolved client workspace path.
382454// Set during loadConfig(), read via getClientWorkspace().
383455let _resolvedClientWorkspace : string | undefined ;
@@ -446,5 +518,6 @@ export function loadConfig(cliArgs: {
446518 clientConfig . experimentalStructuredContent ??
447519 false ,
448520 launch : resolveLaunchFlags ( clientConfig . launch ) ,
521+ hotReload : resolveHotReloadConfig ( hostConfig . hotReload ) ,
449522 } ;
450523}
0 commit comments