@@ -46,6 +46,8 @@ export class McpEventHandler {
4646 #newlyAddedServers: Set < string > = new Set ( )
4747 #fileWatcher: ChokidarFileWatcher
4848 #isProgrammaticChange: boolean = false
49+ #debounceTimer: NodeJS . Timeout | null = null
50+ #lastProgrammaticState: boolean = false
4951
5052 constructor ( features : Features , telemetryService : TelemetryService ) {
5153 this . #features = features
@@ -665,9 +667,8 @@ export class McpEventHandler {
665667 await McpManager . instance . addServer ( serverName , config , configPath , personaPath )
666668 this . #newlyAddedServers. add ( serverName )
667669 }
668- } finally {
669- // Reset flag after operations
670- this . #isProgrammaticChange = false
670+ } catch ( error ) {
671+ this . #features. logging . error ( `Failed to enable MCP server: ${ error } ` )
671672 }
672673
673674 this . #currentEditingServerName = undefined
@@ -694,6 +695,7 @@ export class McpEventHandler {
694695 }
695696
696697 // Stay on add/edit page and show error to user
698+ // Keep isProgrammaticChange true during error handling to prevent file watcher triggers
697699 if ( isEditMode ) {
698700 params . id = 'edit-mcp'
699701 params . title = sanitizedServerName
@@ -708,6 +710,8 @@ export class McpEventHandler {
708710 this . #newlyAddedServers. delete ( serverName )
709711 }
710712
713+ this . #isProgrammaticChange = false
714+
711715 // Go to tools permissions page
712716 return this . #handleOpenMcpServer( { id : 'open-mcp-server' , title : sanitizedServerName } )
713717 }
@@ -812,10 +816,8 @@ export class McpEventHandler {
812816 this . #emitMCPConfigEvent( )
813817 } catch ( error ) {
814818 this . #features. logging . error ( `Failed to enable MCP server: ${ error } ` )
815- } finally {
816- // Reset flag after operations
817- this . #isProgrammaticChange = false
818819 }
820+ this . #isProgrammaticChange = false
819821 return { id : params . id }
820822 }
821823
@@ -845,11 +847,9 @@ export class McpEventHandler {
845847 this . #emitMCPConfigEvent( )
846848 } catch ( error ) {
847849 this . #features. logging . error ( `Failed to disable MCP server: ${ error } ` )
848- } finally {
849- // Reset flag after operations
850- this . #isProgrammaticChange = false
851850 }
852851
852+ this . #isProgrammaticChange = false
853853 return { id : params . id }
854854 }
855855
@@ -867,23 +867,25 @@ export class McpEventHandler {
867867
868868 try {
869869 await McpManager . instance . removeServer ( serverName )
870+
871+ return { id : params . id }
870872 } catch ( error ) {
871873 this . #features. logging . error ( `Failed to delete MCP server: ${ error } ` )
872- } finally {
873- // Reset flag after operations
874874 this . #isProgrammaticChange = false
875+ return { id : params . id }
875876 }
876-
877- return { id : params . id }
878877 }
879878
880879 /**
881880 * Handles edit MCP configuration
882881 */
883882 async #handleEditMcpServer( params : McpServerClickParams , error ?: string ) {
883+ // Set programmatic change flag to true to prevent file watcher triggers
884+ this . #isProgrammaticChange = true
884885 await this . #handleSavePermissionChange( { id : 'save-mcp-permission' } )
885886 const serverName = params . title
886887 if ( ! serverName ) {
888+ this . #isProgrammaticChange = false
887889 return { id : params . id }
888890 }
889891 this . #currentEditingServerName = serverName
@@ -1101,13 +1103,12 @@ export class McpEventHandler {
11011103 this . #pendingPermissionConfig = undefined
11021104
11031105 this . #features. logging . info ( `Applied permission changes for server: ${ serverName } ` )
1106+ this . #isProgrammaticChange = false
11041107 return { id : params . id }
11051108 } catch ( error ) {
11061109 this . #features. logging . error ( `Failed to save MCP permissions: ${ error } ` )
1107- return { id : params . id }
1108- } finally {
1109- // Reset flag after operations
11101110 this . #isProgrammaticChange = false
1111+ return { id : params . id }
11111112 }
11121113 }
11131114
@@ -1331,18 +1332,48 @@ export class McpEventHandler {
13311332
13321333 const allPaths = [ ...configPaths , ...personaPaths ]
13331334
1334- this . #fileWatcher. watchPaths ( allPaths , async ( ) => {
1335- if ( this . #isProgrammaticChange) {
1336- return
1335+ this . #fileWatcher. watchPaths ( allPaths , ( ) => {
1336+ // Store the current programmatic state when the event is triggered
1337+ this . #lastProgrammaticState = this . #isProgrammaticChange
1338+
1339+ // Log the values for debugging
1340+ this . #features. logging . info (
1341+ `File watcher triggered - isProgrammaticChange: ${ this . #isProgrammaticChange} , ` +
1342+ `lastProgrammaticState: ${ this . #lastProgrammaticState} `
1343+ )
1344+
1345+ // Clear any existing timer
1346+ if ( this . #debounceTimer) {
1347+ clearTimeout ( this . #debounceTimer)
13371348 }
1338- await this . #handleRefreshMCPList( { id : 'refresh-mcp-list' } )
1349+
1350+ // Set a new timer with 2 second debounce
1351+ this . #debounceTimer = setTimeout ( async ( ) => {
1352+ // Log the values again when the timer fires
1353+ this . #features. logging . debug (
1354+ `Debounce timer fired - lastProgrammaticState: ${ this . #lastProgrammaticState} `
1355+ )
1356+
1357+ // Only proceed if the stored state allows it
1358+ if ( ! this . #lastProgrammaticState) {
1359+ await this . #handleRefreshMCPList( { id : 'refresh-mcp-list' } )
1360+ } else {
1361+ this . #isProgrammaticChange = false
1362+ this . #features. logging . debug ( 'Skipping refresh due to programmatic change' )
1363+ }
1364+ this . #debounceTimer = null
1365+ } , 2000 )
13391366 } )
13401367 }
13411368
13421369 /**
13431370 * Cleanup file watchers
13441371 */
13451372 dispose ( ) : void {
1373+ if ( this . #debounceTimer) {
1374+ clearTimeout ( this . #debounceTimer)
1375+ this . #debounceTimer = null
1376+ }
13461377 this . #fileWatcher. close ( )
13471378 }
13481379}
0 commit comments