• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mongodb-js / mongodb-mcp-server / 19532221561

20 Nov 2025 09:34AM UTC coverage: 80.12% (-0.2%) from 80.322%
19532221561

Pull #740

github

web-flow
Merge a49fad576 into 40cb62e9f
Pull Request #740: chore: add createSessionConfig hook MCP-294

1338 of 1762 branches covered (75.94%)

Branch coverage included in aggregate %.

10 of 10 new or added lines in 1 file covered. (100.0%)

34 existing lines in 3 files now uncovered.

6388 of 7881 relevant lines covered (81.06%)

73.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.56
/src/tools/atlas/connect/connectCluster.ts
1
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
import { type OperationType, type ToolArgs } from "../../tool.js";
3
import { AtlasToolBase } from "../atlasTool.js";
3✔
4
import { generateSecurePassword } from "../../../helpers/generatePassword.js";
3✔
5
import { LogId } from "../../../common/logger.js";
3✔
6
import { getConnectionString, inspectCluster } from "../../../common/atlas/cluster.js";
3✔
7
import { ensureCurrentIpInAccessList } from "../../../common/atlas/accessListUtils.js";
3✔
8
import type { AtlasClusterConnectionInfo } from "../../../common/connectionManager.js";
9
import { getDefaultRoleFromConfig } from "../../../common/atlas/roles.js";
3✔
10
import { AtlasArgs } from "../../args.js";
3✔
11
import type { ConnectionMetadata } from "../../../telemetry/types.js";
12
import type { ToolCallback } from "@modelcontextprotocol/sdk/server/mcp.js";
13

14
const addedIpAccessListMessage =
3✔
15
    "Note: Your current IP address has been added to the Atlas project's IP access list to enable secure connection.";
3✔
16

17
const createdUserMessage =
3✔
18
    "Note: A temporary user has been created to enable secure connection to the cluster. For more information, see https://dochub.mongodb.org/core/mongodb-mcp-server-tools-considerations\n\nNote to LLM Agent: it is important to include the following link in your response to the user in case they want to get more information about the temporary user created: https://dochub.mongodb.org/core/mongodb-mcp-server-tools-considerations";
3✔
19

20
function sleep(ms: number): Promise<void> {
129✔
21
    return new Promise((resolve) => setTimeout(resolve, ms));
129✔
22
}
129✔
23

24
export const ConnectClusterArgs = {
3✔
25
    projectId: AtlasArgs.projectId().describe("Atlas project ID"),
3✔
26
    clusterName: AtlasArgs.clusterName().describe("Atlas cluster name"),
3✔
27
    connectionType: AtlasArgs.connectionType().describe(
3✔
28
        "Type of connection (standard, private, or privateEndpoint) to an Atlas cluster"
3✔
29
    ),
3✔
30
};
3✔
31

32
export class ConnectClusterTool extends AtlasToolBase {
3✔
33
    public name = "atlas-connect-cluster";
87✔
34
    protected description = "Connect to MongoDB Atlas cluster";
87✔
35
    public operationType: OperationType = "connect";
87✔
36
    protected argsShape = {
87✔
37
        ...ConnectClusterArgs,
87✔
38
    };
87✔
39

40
    private queryConnection(
3✔
41
        projectId: string,
88✔
42
        clusterName: string
88✔
43
    ): "connected" | "disconnected" | "connecting" | "connected-to-other-cluster" | "unknown" {
88✔
44
        if (!this.session.connectedAtlasCluster) {
88✔
45
            if (this.session.isConnectedToMongoDB) {
20!
46
                return "connected-to-other-cluster";
×
47
            }
×
48
            return "disconnected";
20✔
49
        }
20✔
50

51
        const currentConectionState = this.session.connectionManager.currentConnectionState;
68✔
52
        if (
68✔
53
            this.session.connectedAtlasCluster.projectId !== projectId ||
68✔
54
            this.session.connectedAtlasCluster.clusterName !== clusterName
67✔
55
        ) {
88✔
56
            return "connected-to-other-cluster";
1✔
57
        }
1✔
58

59
        switch (currentConectionState.tag) {
67✔
60
            case "connecting":
88!
61
            case "disconnected": // we might still be calling Atlas APIs and not attempted yet to connect to MongoDB, but we are still "connecting"
88!
62
                return "connecting";
×
63
            case "connected":
88✔
64
                return "connected";
3✔
65
            case "errored":
88✔
66
                this.session.logger.debug({
64✔
67
                    id: LogId.atlasConnectFailure,
64✔
68
                    context: "atlas-connect-cluster",
64✔
69
                    message: `error querying cluster: ${currentConectionState.errorReason}`,
64✔
70
                });
64✔
71
                return "unknown";
64✔
72
        }
88✔
73
    }
88✔
74

75
    private async prepareClusterConnection(
3✔
76
        projectId: string,
3✔
77
        clusterName: string,
3✔
78
        connectionType: "standard" | "private" | "privateEndpoint" | undefined = "standard"
3✔
79
    ): Promise<{ connectionString: string; atlas: AtlasClusterConnectionInfo }> {
3✔
80
        const cluster = await inspectCluster(this.session.apiClient, projectId, clusterName);
3✔
81

82
        if (cluster.connectionStrings === undefined) {
3!
83
            throw new Error("Connection strings not available");
×
84
        }
×
85
        const connectionString = getConnectionString(cluster.connectionStrings, connectionType);
3✔
86
        if (connectionString === undefined) {
3!
87
            throw new Error(
×
88
                `Connection string for connection type "${connectionType}" is not available. Please ensure this connection type is set up in Atlas. See https://www.mongodb.com/docs/atlas/connect-to-database-deployment/#connect-to-an-atlas-cluster.`
×
89
            );
×
90
        }
×
91

92
        const username = `mcpUser${Math.floor(Math.random() * 100000)}`;
3✔
93
        const password = await generateSecurePassword();
3✔
94

95
        const expiryDate = new Date(Date.now() + this.config.atlasTemporaryDatabaseUserLifetimeMs);
3✔
96
        const role = getDefaultRoleFromConfig(this.config);
3✔
97

98
        await this.session.apiClient.createDatabaseUser({
3✔
99
            params: {
3✔
100
                path: {
3✔
101
                    groupId: projectId,
3✔
102
                },
3✔
103
            },
3✔
104
            body: {
3✔
105
                databaseName: "admin",
3✔
106
                groupId: projectId,
3✔
107
                roles: [role],
3✔
108
                scopes: [{ type: "CLUSTER", name: clusterName }],
3✔
109
                username,
3✔
110
                password,
3✔
111
                awsIAMType: "NONE",
3✔
112
                ldapAuthType: "NONE",
3✔
113
                oidcAuthType: "NONE",
3✔
114
                x509Type: "NONE",
3✔
115
                deleteAfterDate: expiryDate.toISOString(),
3✔
116
                description:
3✔
117
                    "MDB MCP Temporary user, see https://dochub.mongodb.org/core/mongodb-mcp-server-tools-considerations",
3✔
118
            },
3✔
119
        });
3✔
120

121
        const connectedAtlasCluster = {
3✔
122
            username,
3✔
123
            projectId,
3✔
124
            clusterName,
3✔
125
            expiryDate,
3✔
126
        };
3✔
127

128
        const cn = new URL(connectionString);
3✔
129
        cn.username = username;
3✔
130
        cn.password = password;
3✔
131
        cn.searchParams.set("authSource", "admin");
3✔
132

133
        this.session.keychain.register(username, "user");
3✔
134
        this.session.keychain.register(password, "password");
3✔
135

136
        return { connectionString: cn.toString(), atlas: connectedAtlasCluster };
3✔
137
    }
3✔
138

139
    private async connectToCluster(connectionString: string, atlas: AtlasClusterConnectionInfo): Promise<void> {
3✔
140
        let lastError: Error | undefined = undefined;
3✔
141

142
        this.session.logger.debug({
3✔
143
            id: LogId.atlasConnectAttempt,
3✔
144
            context: "atlas-connect-cluster",
3✔
145
            message: `attempting to connect to cluster: ${this.session.connectedAtlasCluster?.clusterName}`,
3!
146
            noRedaction: true,
3✔
147
        });
3✔
148

149
        // try to connect for about 5 minutes
150
        for (let i = 0; i < 600; i++) {
3✔
151
            try {
50✔
152
                lastError = undefined;
50✔
153

154
                await this.session.connectToMongoDB({ connectionString, atlas });
50✔
155
                break;
3✔
156
            } catch (err: unknown) {
50✔
157
                const error = err instanceof Error ? err : new Error(String(err));
47!
158

159
                lastError = error;
47✔
160

161
                this.session.logger.debug({
47✔
162
                    id: LogId.atlasConnectFailure,
47✔
163
                    context: "atlas-connect-cluster",
47✔
164
                    message: `error connecting to cluster: ${error.message}`,
47✔
165
                });
47✔
166

167
                await sleep(500); // wait for 500ms before retrying
47✔
168
            }
47✔
169

170
            if (
47✔
171
                !this.session.connectedAtlasCluster ||
47✔
172
                this.session.connectedAtlasCluster.projectId !== atlas.projectId ||
47✔
173
                this.session.connectedAtlasCluster.clusterName !== atlas.clusterName
47✔
174
            ) {
50!
175
                throw new Error("Cluster connection aborted");
×
176
            }
×
177
        }
50✔
178

179
        if (lastError) {
3!
180
            if (
×
181
                this.session.connectedAtlasCluster?.projectId === atlas.projectId &&
×
182
                this.session.connectedAtlasCluster?.clusterName === atlas.clusterName &&
×
183
                this.session.connectedAtlasCluster?.username
×
184
            ) {
×
185
                void this.session.apiClient
×
186
                    .deleteDatabaseUser({
×
187
                        params: {
×
188
                            path: {
×
189
                                groupId: this.session.connectedAtlasCluster.projectId,
×
190
                                username: this.session.connectedAtlasCluster.username,
×
191
                                databaseName: "admin",
×
192
                            },
×
193
                        },
×
194
                    })
×
195
                    .catch((err: unknown) => {
×
196
                        const error = err instanceof Error ? err : new Error(String(err));
×
197
                        this.session.logger.debug({
×
198
                            id: LogId.atlasConnectFailure,
×
199
                            context: "atlas-connect-cluster",
×
200
                            message: `error deleting database user: ${error.message}`,
×
201
                        });
×
202
                    });
×
203
            }
×
204
            throw lastError;
×
205
        }
×
206

207
        this.session.logger.debug({
3✔
208
            id: LogId.atlasConnectSucceeded,
3✔
209
            context: "atlas-connect-cluster",
3✔
210
            message: `connected to cluster: ${this.session.connectedAtlasCluster?.clusterName}`,
3✔
211
            noRedaction: true,
3✔
212
        });
3✔
213
    }
3✔
214

215
    protected async execute({
3✔
216
        projectId,
3✔
217
        clusterName,
3✔
218
        connectionType,
3✔
219
    }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
3✔
220
        const ipAccessListUpdated = await ensureCurrentIpInAccessList(this.session.apiClient, projectId);
3✔
221
        let createdUser = false;
3✔
222

223
        const state = this.queryConnection(projectId, clusterName);
3✔
224
        switch (state) {
3✔
225
            case "connected-to-other-cluster":
3✔
226
            case "disconnected": {
3✔
227
                await this.session.disconnect();
3✔
228

229
                const { connectionString, atlas } = await this.prepareClusterConnection(
3✔
230
                    projectId,
3✔
231
                    clusterName,
3✔
232
                    connectionType
3✔
233
                );
3✔
234

235
                createdUser = true;
3✔
236

237
                // try to connect for about 5 minutes asynchronously
238
                void this.connectToCluster(connectionString, atlas).catch((err: unknown) => {
3✔
239
                    const error = err instanceof Error ? err : new Error(String(err));
×
240
                    this.session.logger.error({
×
241
                        id: LogId.atlasConnectFailure,
×
242
                        context: "atlas-connect-cluster",
×
243
                        message: `error connecting to cluster: ${error.message}`,
×
244
                    });
×
245
                });
3✔
246
                break;
3✔
247
            }
3✔
248
            case "connecting":
3!
249
            case "connected":
3!
250
            case "unknown":
3!
251
            default: {
3!
252
                break;
×
253
            }
×
254
        }
3✔
255

256
        for (let i = 0; i < 60; i++) {
3✔
257
            const state = this.queryConnection(projectId, clusterName);
85✔
258
            switch (state) {
85✔
259
                case "connected": {
85✔
260
                    const content: CallToolResult["content"] = [
3✔
261
                        {
3✔
262
                            type: "text",
3✔
263
                            text: `Connected to cluster "${clusterName}".`,
3✔
264
                        },
3✔
265
                    ];
3✔
266

267
                    if (ipAccessListUpdated) {
3✔
268
                        content.push({
3✔
269
                            type: "text",
3✔
270
                            text: addedIpAccessListMessage,
3✔
271
                        });
3✔
272
                    }
3✔
273

274
                    if (createdUser) {
3✔
275
                        content.push({
3✔
276
                            type: "text",
3✔
277
                            text: createdUserMessage,
3✔
278
                        });
3✔
279
                    }
3✔
280

281
                    return { content };
3✔
282
                }
3✔
283
                case "connecting":
85!
284
                case "unknown":
85✔
285
                case "connected-to-other-cluster":
85✔
286
                case "disconnected":
85✔
287
                default: {
85✔
288
                    break;
82✔
289
                }
82✔
290
            }
85✔
291

292
            await sleep(500); // wait 500ms before checking the connection state again
82✔
293
        }
82!
294

UNCOV
295
        const content: CallToolResult["content"] = [
×
UNCOV
296
            {
×
UNCOV
297
                type: "text" as const,
×
UNCOV
298
                text: `Attempting to connect to cluster "${clusterName}"...`,
×
UNCOV
299
            },
×
UNCOV
300
            {
×
UNCOV
301
                type: "text" as const,
×
UNCOV
302
                text: `Warning: Provisioning a user and connecting to the cluster may take more time, please check again in a few seconds.`,
×
UNCOV
303
            },
×
UNCOV
304
        ];
×
305

UNCOV
306
        if (ipAccessListUpdated) {
×
UNCOV
307
            content.push({
×
UNCOV
308
                type: "text" as const,
×
UNCOV
309
                text: addedIpAccessListMessage,
×
UNCOV
310
            });
×
UNCOV
311
        }
×
312

UNCOV
313
        if (createdUser) {
×
UNCOV
314
            content.push({
×
UNCOV
315
                type: "text" as const,
×
UNCOV
316
                text: createdUserMessage,
×
UNCOV
317
            });
×
UNCOV
318
        }
×
319

UNCOV
320
        return { content };
×
321
    }
3✔
322

323
    protected override resolveTelemetryMetadata(
3✔
324
        result: CallToolResult,
×
325
        ...args: Parameters<ToolCallback<typeof this.argsShape>>
×
326
    ): ConnectionMetadata {
×
327
        const parentMetadata = super.resolveTelemetryMetadata(result, ...args);
×
328
        const connectionMetadata = this.getConnectionInfoMetadata();
×
329
        if (connectionMetadata && connectionMetadata.project_id !== undefined) {
×
330
            // delete the project_id from the parent metadata to avoid duplication
331
            delete parentMetadata.project_id;
×
332
        }
×
333
        return { ...parentMetadata, ...connectionMetadata };
×
334
    }
×
335
}
3✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc