@@ -73,6 +73,7 @@ export class McpHub {
7373 private providerRef : WeakRef < ClineProvider >
7474 private disposables : vscode . Disposable [ ] = [ ]
7575 private settingsWatcher ?: vscode . FileSystemWatcher
76+ private projectMcpWatcher ?: vscode . FileSystemWatcher
7677 private fileWatchers : Map < string , FSWatcher > = new Map ( )
7778 private isDisposed : boolean = false
7879 connections : McpConnection [ ] = [ ]
@@ -81,9 +82,55 @@ export class McpHub {
8182 constructor ( provider : ClineProvider ) {
8283 this . providerRef = new WeakRef ( provider )
8384 this . watchMcpSettingsFile ( )
85+ this . watchProjectMcpFile ( )
86+ this . setupWorkspaceFoldersWatcher ( )
8487 this . initializeMcpServers ( )
8588 }
8689
90+ private setupWorkspaceFoldersWatcher ( ) : void {
91+ this . disposables . push (
92+ vscode . workspace . onDidChangeWorkspaceFolders ( async ( ) => {
93+ await this . updateProjectMcpServers ( )
94+ this . watchProjectMcpFile ( )
95+ } ) ,
96+ )
97+ }
98+
99+ private watchProjectMcpFile ( ) : void {
100+ this . projectMcpWatcher ?. dispose ( )
101+
102+ this . projectMcpWatcher = vscode . workspace . createFileSystemWatcher ( "**/.roo/mcp.json" , false , false , false )
103+
104+ this . disposables . push (
105+ this . projectMcpWatcher . onDidChange ( async ( ) => {
106+ await this . updateProjectMcpServers ( )
107+ } ) ,
108+ this . projectMcpWatcher . onDidCreate ( async ( ) => {
109+ await this . updateProjectMcpServers ( )
110+ } ) ,
111+ this . projectMcpWatcher . onDidDelete ( async ( ) => {
112+ await this . cleanupProjectMcpServers ( )
113+ } ) ,
114+ )
115+
116+ this . disposables . push ( this . projectMcpWatcher )
117+ }
118+
119+ private async updateProjectMcpServers ( ) : Promise < void > {
120+ await this . cleanupProjectMcpServers ( )
121+ await this . initializeProjectMcpServers ( )
122+ }
123+
124+ private async cleanupProjectMcpServers ( ) : Promise < void > {
125+ const projectServers = this . connections . filter ( ( conn ) => conn . server . source === "project" )
126+
127+ for ( const conn of projectServers ) {
128+ await this . deleteConnection ( conn . server . name )
129+ }
130+
131+ await this . notifyWebviewOfServerChanges ( )
132+ }
133+
87134 getServers ( ) : McpServer [ ] {
88135 // Only return enabled servers
89136 return this . connections . filter ( ( conn ) => ! conn . server . disabled ) . map ( ( conn ) => conn . server )
@@ -158,16 +205,68 @@ export class McpHub {
158205
159206 private async initializeMcpServers ( ) : Promise < void > {
160207 try {
208+ // 1. Initialize global MCP servers
161209 const settingsPath = await this . getMcpSettingsFilePath ( )
162210 const content = await fs . readFile ( settingsPath , "utf-8" )
163211 const config = JSON . parse ( content )
164- await this . updateServerConnections ( config . mcpServers || { } )
212+ await this . updateServerConnections ( config . mcpServers || { } , "global" )
213+
214+ // 2. Initialize project-level MCP servers
215+ await this . initializeProjectMcpServers ( )
165216 } catch ( error ) {
166217 console . error ( "Failed to initialize MCP servers:" , error )
167218 }
168219 }
169220
170- private async connectToServer ( name : string , config : z . infer < typeof ServerConfigSchema > ) : Promise < void > {
221+ // Get project-level MCP configuration path
222+ private async getProjectMcpPath ( ) : Promise < string | null > {
223+ if ( ! vscode . workspace . workspaceFolders ?. length ) {
224+ return null
225+ }
226+
227+ const workspaceFolder = vscode . workspace . workspaceFolders [ 0 ]
228+ const projectMcpDir = path . join ( workspaceFolder . uri . fsPath , ".roo" )
229+ const projectMcpPath = path . join ( projectMcpDir , "mcp.json" )
230+
231+ try {
232+ await fs . access ( projectMcpPath )
233+ return projectMcpPath
234+ } catch {
235+ return null
236+ }
237+ }
238+
239+ // Initialize project-level MCP servers
240+ private async initializeProjectMcpServers ( ) : Promise < void > {
241+ const projectMcpPath = await this . getProjectMcpPath ( )
242+ if ( ! projectMcpPath ) {
243+ return
244+ }
245+
246+ try {
247+ const content = await fs . readFile ( projectMcpPath , "utf-8" )
248+ const config = JSON . parse ( content )
249+
250+ // Validate configuration structure
251+ const result = McpSettingsSchema . safeParse ( config )
252+ if ( ! result . success ) {
253+ vscode . window . showErrorMessage ( "项目 MCP 配置格式无效" )
254+ return
255+ }
256+
257+ // Update server connections
258+ await this . updateServerConnections ( result . data . mcpServers || { } , "project" )
259+ } catch ( error ) {
260+ console . error ( "Failed to initialize project MCP servers:" , error )
261+ vscode . window . showErrorMessage ( `初始化项目 MCP 服务器失败: ${ error } ` )
262+ }
263+ }
264+
265+ private async connectToServer (
266+ name : string ,
267+ config : z . infer < typeof ServerConfigSchema > ,
268+ source : "global" | "project" = "global" ,
269+ ) : Promise < void > {
171270 // Remove existing connection if it exists
172271 await this . deleteConnection ( name )
173272
@@ -272,6 +371,8 @@ export class McpHub {
272371 config : JSON . stringify ( config ) ,
273372 status : "connecting" ,
274373 disabled : config . disabled ,
374+ source,
375+ projectPath : source === "project" ? vscode . workspace . workspaceFolders ?. [ 0 ] ?. uri . fsPath : undefined ,
275376 } ,
276377 client,
277378 transport,
@@ -366,10 +467,17 @@ export class McpHub {
366467 }
367468 }
368469
369- async updateServerConnections ( newServers : Record < string , any > ) : Promise < void > {
470+ async updateServerConnections (
471+ newServers : Record < string , any > ,
472+ source : "global" | "project" = "global" ,
473+ ) : Promise < void > {
370474 this . isConnecting = true
371475 this . removeAllFileWatchers ( )
372- const currentNames = new Set ( this . connections . map ( ( conn ) => conn . server . name ) )
476+ // Filter connections by source
477+ const currentConnections = this . connections . filter (
478+ ( conn ) => conn . server . source === source || ( ! conn . server . source && source === "global" ) ,
479+ )
480+ const currentNames = new Set ( currentConnections . map ( ( conn ) => conn . server . name ) )
373481 const newNames = new Set ( Object . keys ( newServers ) )
374482
375483 // Delete removed servers
@@ -388,7 +496,7 @@ export class McpHub {
388496 // New server
389497 try {
390498 this . setupFileWatcher ( name , config )
391- await this . connectToServer ( name , config )
499+ await this . connectToServer ( name , config , source )
392500 } catch ( error ) {
393501 console . error ( `Failed to connect to new MCP server ${ name } :` , error )
394502 }
@@ -397,8 +505,8 @@ export class McpHub {
397505 try {
398506 this . setupFileWatcher ( name , config )
399507 await this . deleteConnection ( name )
400- await this . connectToServer ( name , config )
401- console . log ( `Reconnected MCP server with updated config: ${ name } ` )
508+ await this . connectToServer ( name , config , source )
509+ console . log ( `Reconnected ${ source } MCP server with updated config: ${ name } ` )
402510 } catch ( error ) {
403511 console . error ( `Failed to reconnect MCP server ${ name } :` , error )
404512 }
0 commit comments