Skip to content

Commit 758b8da

Browse files
chore: modifies MCP server activation flow MCP-169 (#1114)
1 parent e77b1f3 commit 758b8da

File tree

3 files changed

+265
-150
lines changed

3 files changed

+265
-150
lines changed

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1331,12 +1331,12 @@
13311331
"mdb.mcp.server": {
13321332
"type": "string",
13331333
"enum": [
1334+
"ask",
13341335
"enabled",
1335-
"disabled",
1336-
"ask"
1336+
"disabled"
13371337
],
13381338
"default": "ask",
1339-
"description": "Controls whether MongoDB MCP server starts automatically with the extension or if a permission is requested when connecting to a new connection. If automatic startup is disabled, the server can still be started using the 'MongoDB: Start MCP Server' command."
1339+
"description": "Controls whether MongoDB MCP server starts automatically with the extension and connects to the active connection. If automatic startup is disabled, the server can still be started using the 'MongoDB: Start MCP Server' command."
13401340
},
13411341
"mdb.mcp.exportsPath": {
13421342
"type": "string",

src/mcp/mcpController.ts

Lines changed: 132 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import type { MCPConnectParams } from './mcpConnectionManager';
1717
import { MCPConnectionManager } from './mcpConnectionManager';
1818
import { createMCPConnectionErrorHandler } from './mcpConnectionErrorHandler';
1919

20-
type mcpServerStartupConfig = 'ask' | 'enabled' | 'disabled';
20+
export type McpServerStartupConfig = 'enabled' | 'disabled';
2121

2222
class 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+
3537
export type MCPServerInfo = {
3638
runner: StreamableHttpRunner;
3739
headers: Record<string, string>;
3840
};
41+
3942
export 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

Comments
 (0)