Skip to content

Commit 248052f

Browse files
chore: vscode connection manager implementation
1 parent 6088039 commit 248052f

File tree

3 files changed

+162
-7
lines changed

3 files changed

+162
-7
lines changed

src/mcp/mcpController.ts

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
import * as vscode from 'vscode';
22
import { defaultUserConfig, StreamableHttpRunner } from 'mongodb-mcp-server';
33
import type ConnectionController from '../connectionController';
4-
// import * as os from 'os';
5-
// import * as path from 'path';
4+
import {
5+
VSCodeMCPConnectionManager,
6+
type VSCodeMCPConnectParams,
7+
} from './vsCodeMCPConnectionManager';
8+
import type { CreateConnectionManagerFn, UserConfig } from 'mongodb-mcp-server';
69

710
type mcpServerStartupConfig = 'ask' | 'enabled' | 'disabled';
811

912
export class MCPController {
1013
private didChangeEmitter = new vscode.EventEmitter<void>();
1114
private server?: {
12-
runner: StreamableHttpRunner;
15+
runner: StreamableHttpRunner<VSCodeMCPConnectParams>;
1316
headers: Record<string, string>;
1417
};
18+
private mcpConnectionManager?: VSCodeMCPConnectionManager;
1519

1620
constructor(
1721
private readonly context: vscode.ExtensionContext,
@@ -45,13 +49,24 @@ export class MCPController {
4549
const headers: Record<string, string> = {
4650
authorization: `Bearer ${crypto.randomUUID()}`,
4751
};
48-
const runner = new StreamableHttpRunner({
52+
53+
const mcpConfig: UserConfig = {
4954
...defaultUserConfig,
5055
httpPort: 0,
5156
httpHeaders: headers,
5257
disabledTools: ['connect'],
53-
});
58+
};
59+
60+
const createConnectionManager: CreateConnectionManagerFn<
61+
VSCodeMCPConnectParams
62+
> = async ({ logger }) => {
63+
const connectionManager = (this.mcpConnectionManager =
64+
new VSCodeMCPConnectionManager(logger));
65+
await this.switchConnectionManagerToCurrentConnection();
66+
return connectionManager;
67+
};
5468

69+
const runner = new StreamableHttpRunner(mcpConfig, createConnectionManager);
5570
await runner.start();
5671

5772
this.server = {
@@ -138,8 +153,7 @@ ${jsonConfig}`,
138153

139154
private async onActiveConnectionChanged(): Promise<void> {
140155
if (this.server) {
141-
// Server is created - update the connection information
142-
// this.server.runner.updateConnection();
156+
await this.switchConnectionManagerToCurrentConnection();
143157
return;
144158
}
145159

@@ -191,4 +205,15 @@ ${jsonConfig}`,
191205
await this.startServer();
192206
}
193207
}
208+
209+
private async switchConnectionManagerToCurrentConnection(): Promise<void> {
210+
const connectionId = this.connectionController.getActiveConnectionId();
211+
const mongoClientOptions =
212+
this.connectionController.getMongoClientConnectionOptions();
213+
await this.mcpConnectionManager?.updateConnection({
214+
connectionId,
215+
connectionString: mongoClientOptions?.url,
216+
connectOptions: mongoClientOptions?.options,
217+
});
218+
}
194219
}

src/mcp/mcpLogIds.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { mongoLogId } from 'mongodb-log-writer';
2+
3+
export const MCPLogIds = {
4+
ConnectError: mongoLogId(2_000_001),
5+
DisconnectError: mongoLogId(2_000_002),
6+
UpdateConnectionError: mongoLogId(2_000_003),
7+
} as const;
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import {
2+
ConnectionManager,
3+
type AnyConnectionState,
4+
type CompositeLogger,
5+
type MCPConnectParams,
6+
type ConnectionStateDisconnected,
7+
} from 'mongodb-mcp-server';
8+
import {
9+
NodeDriverServiceProvider,
10+
type DevtoolsConnectOptions,
11+
} from '@mongosh/service-provider-node-driver';
12+
import type { ServiceProvider } from '@mongosh/service-provider-core';
13+
import { isAtlasStream } from 'mongodb-build-info';
14+
import { MCPLogIds } from './mcpLogIds';
15+
16+
export interface VSCodeMCPConnectParams extends MCPConnectParams {
17+
connectionId: string;
18+
connectOptions: DevtoolsConnectOptions;
19+
}
20+
21+
export class VSCodeMCPConnectionManager extends ConnectionManager<VSCodeMCPConnectParams> {
22+
private activeConnectionId: string | null = null;
23+
private activeConnectionProvider: ServiceProvider | null = null;
24+
25+
constructor(private readonly logger: CompositeLogger) {
26+
super();
27+
}
28+
29+
async connect(
30+
connectParams: VSCodeMCPConnectParams,
31+
): Promise<AnyConnectionState> {
32+
try {
33+
const serviceProvider = (this.activeConnectionProvider =
34+
await NodeDriverServiceProvider.connect(
35+
connectParams.connectionString,
36+
connectParams.connectOptions,
37+
));
38+
this.activeConnectionId = connectParams.connectionId;
39+
return this.changeState('connection-succeeded', {
40+
tag: 'connected',
41+
serviceProvider,
42+
});
43+
} catch (error) {
44+
this.logger.error({
45+
id: MCPLogIds.ConnectError,
46+
context: 'VSCodeMCPConnectionManager.connect',
47+
message: error instanceof Error ? error.message : String(error),
48+
});
49+
return this.changeState('connection-errored', {
50+
tag: 'errored',
51+
errorReason: error instanceof Error ? error.message : String(error),
52+
});
53+
}
54+
}
55+
56+
async disconnect(): Promise<ConnectionStateDisconnected> {
57+
try {
58+
await this.activeConnectionProvider?.close(true);
59+
} catch (error) {
60+
this.logger.error({
61+
id: MCPLogIds.DisconnectError,
62+
context: 'VSCodeMCPConnectionManager.disconnect',
63+
message: error instanceof Error ? error.message : String(error),
64+
});
65+
throw error;
66+
} finally {
67+
this.activeConnectionId = null;
68+
this.activeConnectionProvider = null;
69+
}
70+
return Promise.resolve({ tag: 'disconnected' });
71+
}
72+
73+
async updateConnection({
74+
connectionId,
75+
connectionString,
76+
connectOptions,
77+
}: {
78+
connectionId: string | null;
79+
connectionString: string | undefined;
80+
connectOptions: DevtoolsConnectOptions | undefined;
81+
}): Promise<void> {
82+
try {
83+
if (this.activeConnectionId && this.activeConnectionId !== connectionId) {
84+
await this.disconnect();
85+
}
86+
87+
if (this.activeConnectionId === connectionId) {
88+
return;
89+
}
90+
91+
if (!connectionString || !connectOptions || !connectionId) {
92+
this.changeState('connection-errored', {
93+
tag: 'errored',
94+
errorReason:
95+
'MongoDB MCP server cannot establish connection without the required connection string and MongoClientOptions',
96+
});
97+
return;
98+
}
99+
100+
if (isAtlasStream(connectionString)) {
101+
this.changeState('connection-errored', {
102+
tag: 'errored',
103+
errorReason:
104+
'MongoDB MCP server do not support connecting to Atlas Streams',
105+
});
106+
return;
107+
}
108+
109+
await this.connect({
110+
connectionString,
111+
connectOptions,
112+
connectionId,
113+
});
114+
} catch (error) {
115+
this.logger.error({
116+
id: MCPLogIds.UpdateConnectionError,
117+
context: 'VSCodeMCPConnectionManager.updateConnection',
118+
message: error instanceof Error ? error.message : String(error),
119+
});
120+
throw error;
121+
}
122+
}
123+
}

0 commit comments

Comments
 (0)