Skip to content

Commit 809e8cd

Browse files
01Rianmrubens
andauthored
#906 - Add watchPaths option to McpHub for file change detection (#1755)
* #906 Add watchPaths option to McpHub for file change detection * #906 Refactor file watcher management in McpHub add support multiple watchers per server. modified the setupFileWatcher method to properly handle asynchronous operations and prevent unhandled promise rejections. error handling now includes specific error messages that identify exactly where the error occurred. --------- Co-authored-by: Matt Rubens <[email protected]>
1 parent 1c9daf5 commit 809e8cd

File tree

1 file changed

+47
-7
lines changed

1 file changed

+47
-7
lines changed

src/services/mcp/McpHub.ts

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const BaseConfigSchema = z.object({
4242
disabled: z.boolean().optional(),
4343
timeout: z.number().min(1).max(3600).optional().default(60),
4444
alwaysAllow: z.array(z.string()).default([]),
45+
watchPaths: z.array(z.string()).optional(), // paths to watch for changes and restart server
4546
})
4647

4748
// Custom error messages for better user feedback
@@ -102,7 +103,7 @@ export class McpHub {
102103
private providerRef: WeakRef<ClineProvider>
103104
private disposables: vscode.Disposable[] = []
104105
private settingsWatcher?: vscode.FileSystemWatcher
105-
private fileWatchers: Map<string, FSWatcher> = new Map()
106+
private fileWatchers: Map<string, FSWatcher[]> = new Map()
106107
private isDisposed: boolean = false
107108
connections: McpConnection[] = []
108109
isConnecting: boolean = false
@@ -562,29 +563,68 @@ export class McpHub {
562563
}
563564

564565
private setupFileWatcher(name: string, config: z.infer<typeof ServerConfigSchema>) {
566+
// Initialize an empty array for this server if it doesn't exist
567+
if (!this.fileWatchers.has(name)) {
568+
this.fileWatchers.set(name, [])
569+
}
570+
571+
const watchers = this.fileWatchers.get(name) || []
572+
565573
// Only stdio type has args
566574
if (config.type === "stdio") {
575+
// Setup watchers for custom watchPaths if defined
576+
if (config.watchPaths && config.watchPaths.length > 0) {
577+
console.log(`Setting up custom path watchers for ${name} MCP server...`)
578+
const watchPathsWatcher = chokidar.watch(config.watchPaths, {
579+
// persistent: true,
580+
// ignoreInitial: true,
581+
// awaitWriteFinish: true,
582+
})
583+
584+
watchPathsWatcher.on("change", async (changedPath) => {
585+
console.log(`Detected change in custom path ${changedPath}. Restarting server ${name}...`)
586+
try {
587+
await this.restartConnection(name)
588+
} catch (error) {
589+
console.error(`Failed to restart server ${name} after change in ${changedPath}:`, error)
590+
}
591+
})
592+
593+
watchers.push(watchPathsWatcher)
594+
}
595+
596+
// Also setup the fallback build/index.js watcher if applicable
567597
const filePath = config.args?.find((arg: string) => arg.includes("build/index.js"))
568598
if (filePath) {
569-
// we use chokidar instead of onDidSaveTextDocument because it doesn't require the file to be open in the editor. The settings config is better suited for onDidSave since that will be manually updated by the user or Cline (and we want to detect save events, not every file change)
570-
const watcher = chokidar.watch(filePath, {
599+
console.log(`Setting up build/index.js watcher for ${name} MCP server...`)
600+
// we use chokidar instead of onDidSaveTextDocument because it doesn't require the file to be open in the editor
601+
const indexJsWatcher = chokidar.watch(filePath, {
571602
// persistent: true,
572603
// ignoreInitial: true,
573604
// awaitWriteFinish: true, // This helps with atomic writes
574605
})
575606

576-
watcher.on("change", () => {
607+
indexJsWatcher.on("change", async () => {
577608
console.log(`Detected change in ${filePath}. Restarting server ${name}...`)
578-
this.restartConnection(name)
609+
try {
610+
await this.restartConnection(name)
611+
} catch (error) {
612+
console.error(`Failed to restart server ${name} after change in ${filePath}:`, error)
613+
}
579614
})
580615

581-
this.fileWatchers.set(name, watcher)
616+
watchers.push(indexJsWatcher)
617+
}
618+
619+
// Update the fileWatchers map with all watchers for this server
620+
if (watchers.length > 0) {
621+
this.fileWatchers.set(name, watchers)
582622
}
583623
}
584624
}
585625

586626
private removeAllFileWatchers() {
587-
this.fileWatchers.forEach((watcher) => watcher.close())
627+
this.fileWatchers.forEach((watchers) => watchers.forEach((watcher) => watcher.close()))
588628
this.fileWatchers.clear()
589629
}
590630

0 commit comments

Comments
 (0)