@@ -8,12 +8,7 @@ import { inspectCluster } from "../../../common/atlas/cluster.js";
88import { ensureCurrentIpInAccessList } from "../../../common/atlas/accessListUtils.js" ;
99import { AtlasClusterConnectionInfo } from "../../../common/connectionManager.js" ;
1010
11- // Connection configuration constants
12- const USER_EXPIRY_MS = 1000 * 60 * 60 * 12 ; // 12 hours
13- const CONNECTION_RETRY_ATTEMPTS = 600 ; // 5 minutes (600 * 500ms = 5 minutes)
14- const CONNECTION_RETRY_DELAY_MS = 500 ; // 500ms between retries
15- const CONNECTION_CHECK_ATTEMPTS = 60 ; // 30 seconds (60 * 500ms = 30 seconds)
16- const CONNECTION_CHECK_DELAY_MS = 500 ; // 500ms between connection state checks
11+ const EXPIRY_MS = 1000 * 60 * 60 * 12 ; // 12 hours
1712
1813function sleep ( ms : number ) : Promise < void > {
1914 return new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ;
@@ -28,45 +23,6 @@ export class ConnectClusterTool extends AtlasToolBase {
2823 clusterName : z . string ( ) . describe ( "Atlas cluster name" ) ,
2924 } ;
3025
31- private determineReadOnlyRole ( ) : boolean {
32- if ( this . config . readOnly ) return true ;
33-
34- const disabledTools = this . config . disabledTools || [ ] ;
35- const hasWriteAccess =
36- ! disabledTools . includes ( "create" ) && ! disabledTools . includes ( "update" ) && ! disabledTools . includes ( "delete" ) ;
37- const hasReadAccess = ! disabledTools . includes ( "read" ) && ! disabledTools . includes ( "metadata" ) ;
38-
39- return ! hasWriteAccess && hasReadAccess ;
40- }
41-
42- private isConnectedToOtherCluster ( projectId : string , clusterName : string ) : boolean {
43- return (
44- this . session . isConnectedToMongoDB &&
45- ( ! this . session . connectedAtlasCluster ||
46- this . session . connectedAtlasCluster . projectId !== projectId ||
47- this . session . connectedAtlasCluster . clusterName !== clusterName )
48- ) ;
49- }
50-
51- private getConnectionState ( ) : "connected" | "connecting" | "disconnected" | "errored" {
52- const state = this . session . connectionManager . currentConnectionState ;
53- switch ( state . tag ) {
54- case "connected" :
55- return "connected" ;
56- case "connecting" :
57- return "connecting" ;
58- case "disconnected" :
59- return "disconnected" ;
60- case "errored" :
61- return "errored" ;
62- }
63- }
64-
65- private getErrorReason ( ) : string | undefined {
66- const state = this . session . connectionManager . currentConnectionState ;
67- return state . tag === "errored" ? state . errorReason : undefined ;
68- }
69-
7026 private queryConnection (
7127 projectId : string ,
7228 clusterName : string
@@ -78,40 +34,52 @@ export class ConnectClusterTool extends AtlasToolBase {
7834 return "disconnected" ;
7935 }
8036
81- if ( this . isConnectedToOtherCluster ( projectId , clusterName ) ) {
37+ const currentConectionState = this . session . connectionManager . currentConnectionState ;
38+ if (
39+ this . session . connectedAtlasCluster . projectId !== projectId ||
40+ this . session . connectedAtlasCluster . clusterName !== clusterName
41+ ) {
8242 return "connected-to-other-cluster" ;
8343 }
8444
85- const connectionState = this . getConnectionState ( ) ;
86- switch ( connectionState ) {
45+ switch ( currentConectionState . tag ) {
8746 case "connecting" :
8847 case "disconnected" : // we might still be calling Atlas APIs and not attempted yet to connect to MongoDB, but we are still "connecting"
8948 return "connecting" ;
9049 case "connected" :
9150 return "connected" ;
9251 case "errored" :
93- const errorReason = this . getErrorReason ( ) ;
9452 this . session . logger . debug ( {
9553 id : LogId . atlasConnectFailure ,
9654 context : "atlas-connect-cluster" ,
97- message : `error querying cluster: ${ errorReason || "unknown error" } ` ,
55+ message : `error querying cluster: ${ currentConectionState . errorReason } ` ,
9856 } ) ;
9957 return "unknown" ;
10058 }
10159 }
10260
103- private async createDatabaseUser (
61+ private async prepareClusterConnection (
10462 projectId : string ,
105- clusterName : string ,
106- readOnly : boolean
107- ) : Promise < {
108- username : string ;
109- password : string ;
110- expiryDate : Date ;
111- } > {
63+ clusterName : string
64+ ) : Promise < { connectionString : string ; atlas : AtlasClusterConnectionInfo } > {
65+ const cluster = await inspectCluster ( this . session . apiClient , projectId , clusterName ) ;
66+
67+ if ( ! cluster . connectionString ) {
68+ throw new Error ( "Connection string not available" ) ;
69+ }
70+
11271 const username = `mcpUser${ Math . floor ( Math . random ( ) * 100000 ) } ` ;
11372 const password = await generateSecurePassword ( ) ;
114- const expiryDate = new Date ( Date . now ( ) + USER_EXPIRY_MS ) ;
73+
74+ const expiryDate = new Date ( Date . now ( ) + EXPIRY_MS ) ;
75+
76+ const readOnly =
77+ this . config . readOnly ||
78+ ( this . config . disabledTools ?. includes ( "create" ) &&
79+ this . config . disabledTools ?. includes ( "update" ) &&
80+ this . config . disabledTools ?. includes ( "delete" ) &&
81+ ! this . config . disabledTools ?. includes ( "read" ) &&
82+ ! this . config . disabledTools ?. includes ( "metadata" ) ) ;
11583
11684 const roleName = readOnly ? "readAnyDatabase" : "readWriteAnyDatabase" ;
11785
@@ -141,40 +109,19 @@ export class ConnectClusterTool extends AtlasToolBase {
141109 } ,
142110 } ) ;
143111
144- return { username, password, expiryDate } ;
145- }
146-
147- private buildConnectionString ( clusterConnectionString : string , username : string , password : string ) : string {
148- const cn = new URL ( clusterConnectionString ) ;
149- cn . username = username ;
150- cn . password = password ;
151- cn . searchParams . set ( "authSource" , "admin" ) ;
152- return cn . toString ( ) ;
153- }
154-
155- private async prepareClusterConnection (
156- projectId : string ,
157- clusterName : string
158- ) : Promise < { connectionString : string ; atlas : AtlasClusterConnectionInfo } > {
159- const cluster = await inspectCluster ( this . session . apiClient , projectId , clusterName ) ;
160-
161- if ( ! cluster . connectionString ) {
162- throw new Error ( "Connection string not available" ) ;
163- }
164-
165- const readOnly = this . determineReadOnlyRole ( ) ;
166- const { username, password, expiryDate } = await this . createDatabaseUser ( projectId , clusterName , readOnly ) ;
167-
168112 const connectedAtlasCluster = {
169113 username,
170114 projectId,
171115 clusterName,
172116 expiryDate,
173117 } ;
174118
175- const connectionString = this . buildConnectionString ( cluster . connectionString , username , password ) ;
119+ const cn = new URL ( cluster . connectionString ) ;
120+ cn . username = username ;
121+ cn . password = password ;
122+ cn . searchParams . set ( "authSource" , "admin" ) ;
176123
177- return { connectionString, atlas : connectedAtlasCluster } ;
124+ return { connectionString : cn . toString ( ) , atlas : connectedAtlasCluster } ;
178125 }
179126
180127 private async connectToCluster ( connectionString : string , atlas : AtlasClusterConnectionInfo ) : Promise < void > {
@@ -188,7 +135,7 @@ export class ConnectClusterTool extends AtlasToolBase {
188135 } ) ;
189136
190137 // try to connect for about 5 minutes
191- for ( let i = 0 ; i < CONNECTION_RETRY_ATTEMPTS ; i ++ ) {
138+ for ( let i = 0 ; i < 600 ; i ++ ) {
192139 try {
193140 lastError = undefined ;
194141
@@ -205,7 +152,7 @@ export class ConnectClusterTool extends AtlasToolBase {
205152 message : `error connecting to cluster: ${ error . message } ` ,
206153 } ) ;
207154
208- await sleep ( CONNECTION_RETRY_DELAY_MS ) ; // wait for 500ms before retrying
155+ await sleep ( 500 ) ; // wait for 500ms before retrying
209156 }
210157
211158 if (
@@ -218,7 +165,30 @@ export class ConnectClusterTool extends AtlasToolBase {
218165 }
219166
220167 if ( lastError ) {
221- await this . cleanupDatabaseUserOnFailure ( atlas ) ;
168+ if (
169+ this . session . connectedAtlasCluster ?. projectId === atlas . projectId &&
170+ this . session . connectedAtlasCluster ?. clusterName === atlas . clusterName &&
171+ this . session . connectedAtlasCluster ?. username
172+ ) {
173+ void this . session . apiClient
174+ . deleteDatabaseUser ( {
175+ params : {
176+ path : {
177+ groupId : this . session . connectedAtlasCluster . projectId ,
178+ username : this . session . connectedAtlasCluster . username ,
179+ databaseName : "admin" ,
180+ } ,
181+ } ,
182+ } )
183+ . catch ( ( err : unknown ) => {
184+ const error = err instanceof Error ? err : new Error ( String ( err ) ) ;
185+ this . session . logger . debug ( {
186+ id : LogId . atlasConnectFailure ,
187+ context : "atlas-connect-cluster" ,
188+ message : `error deleting database user: ${ error . message } ` ,
189+ } ) ;
190+ } ) ;
191+ }
222192 throw lastError ;
223193 }
224194
@@ -230,37 +200,9 @@ export class ConnectClusterTool extends AtlasToolBase {
230200 } ) ;
231201 }
232202
233- private async cleanupDatabaseUserOnFailure ( atlas : AtlasClusterConnectionInfo ) : Promise < void > {
234- const currentCluster = this . session . connectedAtlasCluster ;
235- if (
236- currentCluster ?. projectId === atlas . projectId &&
237- currentCluster ?. clusterName === atlas . clusterName &&
238- currentCluster ?. username
239- ) {
240- try {
241- await this . session . apiClient . deleteDatabaseUser ( {
242- params : {
243- path : {
244- groupId : currentCluster . projectId ,
245- username : currentCluster . username ,
246- databaseName : "admin" ,
247- } ,
248- } ,
249- } ) ;
250- } catch ( err : unknown ) {
251- const error = err instanceof Error ? err : new Error ( String ( err ) ) ;
252- this . session . logger . debug ( {
253- id : LogId . atlasConnectFailure ,
254- context : "atlas-connect-cluster" ,
255- message : `error deleting database user: ${ error . message } ` ,
256- } ) ;
257- }
258- }
259- }
260-
261203 protected async execute ( { projectId, clusterName } : ToolArgs < typeof this . argsShape > ) : Promise < CallToolResult > {
262204 await ensureCurrentIpInAccessList ( this . session . apiClient , projectId ) ;
263- for ( let i = 0 ; i < CONNECTION_CHECK_ATTEMPTS ; i ++ ) {
205+ for ( let i = 0 ; i < 60 ; i ++ ) {
264206 const state = this . queryConnection ( projectId , clusterName ) ;
265207 switch ( state ) {
266208 case "connected" : {
@@ -284,21 +226,19 @@ export class ConnectClusterTool extends AtlasToolBase {
284226 const { connectionString, atlas } = await this . prepareClusterConnection ( projectId , clusterName ) ;
285227
286228 // try to connect for about 5 minutes asynchronously
287- try {
288- await this . connectToCluster ( connectionString , atlas ) ;
289- } catch ( err : unknown ) {
229+ void this . connectToCluster ( connectionString , atlas ) . catch ( ( err : unknown ) => {
290230 const error = err instanceof Error ? err : new Error ( String ( err ) ) ;
291231 this . session . logger . error ( {
292232 id : LogId . atlasConnectFailure ,
293233 context : "atlas-connect-cluster" ,
294234 message : `error connecting to cluster: ${ error . message } ` ,
295235 } ) ;
296- }
236+ } ) ;
297237 break ;
298238 }
299239 }
300240
301- await sleep ( CONNECTION_CHECK_DELAY_MS ) ;
241+ await sleep ( 500 ) ;
302242 }
303243
304244 return {
0 commit comments