Skip to content

Commit 517a3cb

Browse files
committed
refactor: remove mcp-server-watcher and implement requestPipeline for unified tool execution
- Deleted the mcp-server-watcher.ts file, which handled source change detection and hot-reload logic. - Introduced requestPipeline.ts to manage a unified FIFO tool execution queue with hot-reload awareness. - Simplified the hot-reload mechanism by consolidating multiple mutexes into a single serialization process. - Enhanced error handling and logging for tool execution and build failures.
1 parent f15b7a2 commit 517a3cb

7 files changed

Lines changed: 564 additions & 1288 deletions

File tree

src/Mutex.ts

Lines changed: 0 additions & 41 deletions
This file was deleted.

src/config.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

184210
function 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+
204238
function 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().
383455
let _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

Comments
 (0)