@@ -113,7 +113,59 @@ export class McpHub {
113113 this . watchMcpSettingsFile ( )
114114 this . watchProjectMcpFile ( )
115115 this . setupWorkspaceFoldersWatcher ( )
116- this . initializeMcpServers ( )
116+ this . initializeGlobalMcpServers ( )
117+ this . initializeProjectMcpServers ( )
118+ }
119+
120+ public setupWorkspaceFoldersWatcher ( ) : void {
121+ // Skip if test environment is detected
122+ if ( process . env . NODE_ENV === "test" || process . env . JEST_WORKER_ID !== undefined ) {
123+ return
124+ }
125+ this . disposables . push (
126+ vscode . workspace . onDidChangeWorkspaceFolders ( async ( ) => {
127+ await this . updateProjectMcpServers ( )
128+ this . watchProjectMcpFile ( )
129+ } ) ,
130+ )
131+ }
132+
133+ private watchProjectMcpFile ( ) : void {
134+ this . projectMcpWatcher ?. dispose ( )
135+
136+ this . projectMcpWatcher = vscode . workspace . createFileSystemWatcher ( "**/.roo/mcp.json" , false , false , false )
137+
138+ this . disposables . push (
139+ this . projectMcpWatcher . onDidChange ( async ( ) => {
140+ await this . updateProjectMcpServers ( )
141+ } ) ,
142+ this . projectMcpWatcher . onDidCreate ( async ( ) => {
143+ await this . updateProjectMcpServers ( )
144+ } ) ,
145+ this . projectMcpWatcher . onDidDelete ( async ( ) => {
146+ await this . cleanupProjectMcpServers ( )
147+ } ) ,
148+ )
149+
150+ this . disposables . push ( this . projectMcpWatcher )
151+ }
152+
153+ private async updateProjectMcpServers ( ) : Promise < void > {
154+ // Only clean up and initialize project servers, not affecting global servers
155+ await this . cleanupProjectMcpServers ( )
156+ await this . initializeProjectMcpServers ( )
157+ }
158+
159+ private async cleanupProjectMcpServers ( ) : Promise < void > {
160+ // Only filter and delete project servers
161+ const projectServers = this . connections . filter ( ( conn ) => conn . server . source === "project" )
162+
163+ for ( const conn of projectServers ) {
164+ await this . deleteConnection ( conn . server . name )
165+ }
166+
167+ // Notify webview of changes after cleanup
168+ await this . notifyWebviewOfServerChanges ( )
117169 }
118170
119171 /**
@@ -301,7 +353,8 @@ export class McpHub {
301353 return
302354 }
303355 try {
304- await this . updateServerConnections ( result . data . mcpServers || { } )
356+ // Only update global servers when global settings change
357+ await this . updateServerConnections ( result . data . mcpServers || { } , "global" )
305358 } catch ( error ) {
306359 this . showErrorMessage ( "Failed to process MCP settings change" , error )
307360 }
@@ -310,9 +363,9 @@ export class McpHub {
310363 )
311364 }
312365
313- private async initializeMcpServers ( ) : Promise < void > {
366+ private async initializeGlobalMcpServers ( ) : Promise < void > {
314367 try {
315- // 1. Initialize global MCP servers
368+ // Initialize global MCP servers
316369 const settingsPath = await this . getMcpSettingsFilePath ( )
317370 const content = await fs . readFile ( settingsPath , "utf-8" )
318371 let config : any
@@ -343,13 +396,13 @@ export class McpHub {
343396
344397 // Still try to connect with the raw config, but show warnings
345398 try {
346- await this . updateServerConnections ( config . mcpServers || { } )
399+ await this . updateServerConnections ( config . mcpServers || { } , "global" )
347400 } catch ( error ) {
348- this . showErrorMessage ( "Failed to initialize MCP servers with raw config" , error )
401+ this . showErrorMessage ( "Failed to initialize global MCP servers with raw config" , error )
349402 }
350403 }
351404 } catch ( error ) {
352- this . showErrorMessage ( "Failed to initialize MCP servers" , error )
405+ this . showErrorMessage ( "Failed to initialize global MCP servers" , error )
353406 }
354407 }
355408
@@ -637,7 +690,12 @@ export class McpHub {
637690
638691 // Update or add servers
639692 for ( const [ name , config ] of Object . entries ( newServers ) ) {
640- const currentConnection = this . connections . find ( ( conn ) => conn . server . name === name )
693+ // Only consider connections that match the current source
694+ const currentConnection = this . connections . find (
695+ ( conn ) =>
696+ conn . server . name === name &&
697+ ( conn . server . source === source || ( ! conn . server . source && source === "global" ) ) ,
698+ )
641699
642700 // Validate and transform the config
643701 let validatedConfig : z . infer < typeof ServerConfigSchema >
@@ -740,20 +798,49 @@ export class McpHub {
740798 }
741799
742800 private async notifyWebviewOfServerChanges ( ) : Promise < void > {
743- // servers should always be sorted in the order they are defined in the settings file
801+ // Get global server order from settings file
744802 const settingsPath = await this . getMcpSettingsFilePath ( )
745803 const content = await fs . readFile ( settingsPath , "utf-8" )
746804 const config = JSON . parse ( content )
747- const serverOrder = Object . keys ( config . mcpServers || { } )
805+ const globalServerOrder = Object . keys ( config . mcpServers || { } )
806+
807+ // Get project server order if available
808+ const projectMcpPath = await this . getProjectMcpPath ( )
809+ let projectServerOrder : string [ ] = [ ]
810+ if ( projectMcpPath ) {
811+ try {
812+ const projectContent = await fs . readFile ( projectMcpPath , "utf-8" )
813+ const projectConfig = JSON . parse ( projectContent )
814+ projectServerOrder = Object . keys ( projectConfig . mcpServers || { } )
815+ } catch ( error ) {
816+ console . error ( "Failed to read project MCP config:" , error )
817+ }
818+ }
819+
820+ // Sort connections: first global servers in their defined order, then project servers in their defined order
821+ const sortedConnections = [ ...this . connections ] . sort ( ( a , b ) => {
822+ const aIsGlobal = a . server . source === "global" || ! a . server . source
823+ const bIsGlobal = b . server . source === "global" || ! b . server . source
824+
825+ // If both are global or both are project, sort by their respective order
826+ if ( aIsGlobal && bIsGlobal ) {
827+ const indexA = globalServerOrder . indexOf ( a . server . name )
828+ const indexB = globalServerOrder . indexOf ( b . server . name )
829+ return indexA - indexB
830+ } else if ( ! aIsGlobal && ! bIsGlobal ) {
831+ const indexA = projectServerOrder . indexOf ( a . server . name )
832+ const indexB = projectServerOrder . indexOf ( b . server . name )
833+ return indexA - indexB
834+ }
835+
836+ // Global servers come before project servers
837+ return aIsGlobal ? - 1 : 1
838+ } )
839+
840+ // Send sorted servers to webview
748841 await this . providerRef . deref ( ) ?. postMessageToWebview ( {
749842 type : "mcpServers" ,
750- mcpServers : [ ...this . connections ]
751- . sort ( ( a , b ) => {
752- const indexA = serverOrder . indexOf ( a . server . name )
753- const indexB = serverOrder . indexOf ( b . server . name )
754- return indexA - indexB
755- } )
756- . map ( ( connection ) => connection . server ) ,
843+ mcpServers : sortedConnections . map ( ( connection ) => connection . server ) ,
757844 } )
758845 }
759846
0 commit comments