@@ -17,7 +17,7 @@ import type { MCPConnectParams } from './mcpConnectionManager';
1717import { MCPConnectionManager } from './mcpConnectionManager' ;
1818import { createMCPConnectionErrorHandler } from './mcpConnectionErrorHandler' ;
1919
20- type mcpServerStartupConfig = 'ask' | 'enabled' | 'disabled' ;
20+ export type McpServerStartupConfig = 'enabled' | 'disabled' ;
2121
2222class VSCodeMCPLogger extends LoggerBase {
2323 private readonly _logger = createLogger ( 'mcp-server' ) ;
@@ -32,10 +32,13 @@ class VSCodeMCPLogger extends LoggerBase {
3232 }
3333}
3434
35+ const logger = createLogger ( 'mcp-controller' ) ;
36+
3537export type MCPServerInfo = {
3638 runner : StreamableHttpRunner ;
3739 headers : Record < string , string > ;
3840} ;
41+
3942export class MCPController {
4043 private didChangeEmitter = new vscode . EventEmitter < void > ( ) ;
4144 private server ?: MCPServerInfo ;
@@ -58,60 +61,127 @@ export class MCPController {
5861 ) ;
5962 }
6063
61- public activate ( ) : Promise < void > {
64+ public async activate ( ) : Promise < void > {
6265 this . connectionController . addEventListener (
6366 'ACTIVE_CONNECTION_CHANGED' ,
6467 ( ) => {
6568 void this . onActiveConnectionChanged ( ) ;
6669 } ,
6770 ) ;
6871
69- return Promise . resolve ( ) ;
72+ if ( this . shouldStartMCPServer ( ) ) {
73+ await this . startServer ( ) ;
74+ void this . notifyOnFirstStart ( ) ;
75+ }
7076 }
7177
7278 public async startServer ( ) : Promise < void > {
73- const headers : Record < string , string > = {
74- authorization : `Bearer ${ crypto . randomUUID ( ) } ` ,
75- } ;
76-
77- const mcpConfig : UserConfig = {
78- ...defaultUserConfig ,
79- httpPort : 0 ,
80- httpHeaders : headers ,
81- disabledTools : [ 'connect' ] ,
82- loggers : [ 'mcp' ] ,
83- } ;
84-
85- const createConnectionManager : ConnectionManagerFactoryFn = async ( {
86- logger,
87- } ) => {
88- const connectionManager = ( this . mcpConnectionManager =
89- new MCPConnectionManager ( logger ) ) ;
90- await this . switchConnectionManagerToCurrentConnection ( ) ;
91- return connectionManager ;
92- } ;
93-
94- const runner = new StreamableHttpRunner ( {
95- userConfig : mcpConfig ,
96- createConnectionManager,
97- connectionErrorHandler : createMCPConnectionErrorHandler (
98- this . connectionController ,
99- ) ,
100- additionalLoggers : [ new VSCodeMCPLogger ( ) ] ,
101- } ) ;
102- await runner . start ( ) ;
103-
104- this . server = {
105- runner,
106- headers,
107- } ;
108- this . didChangeEmitter . fire ( ) ;
79+ try {
80+ const headers : Record < string , string > = {
81+ authorization : `Bearer ${ crypto . randomUUID ( ) } ` ,
82+ } ;
83+
84+ const mcpConfig : UserConfig = {
85+ ...defaultUserConfig ,
86+ httpPort : 0 ,
87+ httpHeaders : headers ,
88+ disabledTools : [ 'connect' ] ,
89+ loggers : [ 'mcp' ] ,
90+ } ;
91+
92+ const createConnectionManager : ConnectionManagerFactoryFn = async ( {
93+ logger,
94+ } ) => {
95+ const connectionManager = ( this . mcpConnectionManager =
96+ new MCPConnectionManager ( logger ) ) ;
97+ await this . switchConnectionManagerToCurrentConnection ( ) ;
98+ return connectionManager ;
99+ } ;
100+
101+ const runner = new StreamableHttpRunner ( {
102+ userConfig : mcpConfig ,
103+ createConnectionManager,
104+ connectionErrorHandler : createMCPConnectionErrorHandler (
105+ this . connectionController ,
106+ ) ,
107+ additionalLoggers : [ new VSCodeMCPLogger ( ) ] ,
108+ } ) ;
109+ await runner . start ( ) ;
110+
111+ this . server = {
112+ runner,
113+ headers,
114+ } ;
115+ this . didChangeEmitter . fire ( ) ;
116+ } catch ( error ) {
117+ // In case of errors we don't want VSCode extension process to crash so we
118+ // silence MCP start errors and instead log them for debugging.
119+ logger . error ( 'Error when attempting to start MCP server' , error ) ;
120+ }
109121 }
110122
111123 public async stopServer ( ) : Promise < void > {
112- await this . server ?. runner . close ( ) ;
113- this . server = undefined ;
114- this . didChangeEmitter . fire ( ) ;
124+ try {
125+ await this . server ?. runner . close ( ) ;
126+ this . server = undefined ;
127+ this . didChangeEmitter . fire ( ) ;
128+ } catch ( error ) {
129+ logger . error ( 'Error when attempting to close the MCP server' , error ) ;
130+ }
131+ }
132+
133+ private async notifyOnFirstStart ( ) : Promise < void > {
134+ try {
135+ if ( ! this . server ) {
136+ // Server was never started so no need to notify
137+ return ;
138+ }
139+
140+ const serverStartConfig = this . getMCPAutoStartConfig ( ) ;
141+
142+ // If the config value is one of the following values means they are
143+ // intentional (either set by user or by this function itself) and we
144+ // should not notify in that case.
145+ const shouldNotNotify =
146+ serverStartConfig === 'enabled' || serverStartConfig === 'disabled' ;
147+
148+ if ( shouldNotNotify ) {
149+ return ;
150+ }
151+
152+ // We set the auto start already to enabled to not prompt user again for
153+ // this on the next boot. We do it this way because chances are that the
154+ // user might not act on the notification in which case the final update
155+ // will never happen.
156+ await this . setMCPAutoStartConfig ( 'enabled' ) ;
157+ let selectedServerStartConfig : McpServerStartupConfig = 'enabled' ;
158+
159+ const prompt = await vscode . window . showInformationMessage (
160+ 'MongoDB MCP server started automatically and will connect to your active connection. Would you like to keep or disable automatic startup?' ,
161+ 'Keep' ,
162+ 'Disable' ,
163+ ) ;
164+
165+ switch ( prompt ) {
166+ case 'Keep' :
167+ default :
168+ // The default happens only when users explicity dismiss the
169+ // notification.
170+ selectedServerStartConfig = 'enabled' ;
171+ break ;
172+ case 'Disable' : {
173+ selectedServerStartConfig = 'disabled' ;
174+ await this . stopServer ( ) ;
175+ }
176+ }
177+
178+ await this . setMCPAutoStartConfig ( selectedServerStartConfig ) ;
179+ } catch ( error ) {
180+ logger . error (
181+ 'Error while attempting to emit MCP server started notification' ,
182+ error ,
183+ ) ;
184+ }
115185 }
116186
117187 public async openServerConfig ( ) : Promise < boolean > {
@@ -185,58 +255,10 @@ ${jsonConfig}`,
185255 }
186256
187257 private async onActiveConnectionChanged ( ) : Promise < void > {
188- if ( this . server ) {
189- await this . switchConnectionManagerToCurrentConnection ( ) ;
190- return ;
191- }
192-
193- if ( ! this . connectionController . getActiveConnectionId ( ) ) {
194- // No connection, don't prompt the user
258+ if ( ! this . server ) {
195259 return ;
196260 }
197-
198- const serverStartConfig = vscode . workspace
199- . getConfiguration ( 'mdb' )
200- . get < mcpServerStartupConfig > ( 'mcp.server' ) ;
201-
202- let shouldStartServer = false ;
203- switch ( serverStartConfig ) {
204- case 'enabled' :
205- shouldStartServer = true ;
206- break ;
207- case 'disabled' :
208- break ;
209- default :
210- const prompt = await vscode . window . showInformationMessage (
211- 'Do you want to start the MongoDB MCP server automatically when connected to MongoDB?' ,
212- 'Yes' ,
213- 'No' ,
214- ) ;
215-
216- switch ( prompt ) {
217- case 'Yes' :
218- shouldStartServer = true ;
219- break ;
220- case 'No' :
221- shouldStartServer = false ;
222- break ;
223- default :
224- // User canceled/dismissed the notification - don't do anything.
225- return ;
226- }
227-
228- await vscode . workspace
229- . getConfiguration ( 'mdb' )
230- . update (
231- 'mcp.server' ,
232- shouldStartServer ? 'enabled' : 'disabled' ,
233- true ,
234- ) ;
235- }
236-
237- if ( shouldStartServer ) {
238- await this . startServer ( ) ;
239- }
261+ await this . switchConnectionManagerToCurrentConnection ( ) ;
240262 }
241263
242264 private async switchConnectionManagerToCurrentConnection ( ) : Promise < void > {
@@ -254,4 +276,22 @@ ${jsonConfig}`,
254276 : undefined ;
255277 await this . mcpConnectionManager ?. updateConnection ( connectParams ) ;
256278 }
279+
280+ private shouldStartMCPServer ( ) : boolean {
281+ return this . getMCPAutoStartConfig ( ) !== 'disabled' ;
282+ }
283+
284+ private getMCPAutoStartConfig ( ) : McpServerStartupConfig | undefined {
285+ return vscode . workspace
286+ . getConfiguration ( 'mdb' )
287+ . get < McpServerStartupConfig > ( 'mcp.server' ) ;
288+ }
289+
290+ private async setMCPAutoStartConfig (
291+ config : McpServerStartupConfig ,
292+ ) : Promise < void > {
293+ await vscode . workspace
294+ . getConfiguration ( 'mdb' )
295+ . update ( 'mcp.server' , config , true ) ;
296+ }
257297}
0 commit comments