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

mongodb-js / mongodb-mcp-server / 19166075826

07 Nov 2025 10:49AM UTC coverage: 80.026% (-0.1%) from 80.151%
19166075826

Pull #716

github

web-flow
Merge b5964dd5c into 454e81617
Pull Request #716: chore: add connection metadata to telemetry

1363 of 1802 branches covered (75.64%)

Branch coverage included in aggregate %.

21 of 51 new or added lines in 7 files covered. (41.18%)

8 existing lines in 1 file now uncovered.

6490 of 8011 relevant lines covered (81.01%)

70.28 hits per line

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

70.63
/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> {
8✔
21
    return new Promise((resolve) => setTimeout(resolve, ms));
8✔
22
}
8✔
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";
85✔
34
    protected description = "Connect to MongoDB Atlas cluster";
85✔
35
    public operationType: OperationType = "connect";
85✔
36
    protected argsShape = {
85✔
37
        ...ConnectClusterArgs,
85✔
38
    };
85✔
39

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

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

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

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

82
        if (cluster.connectionStrings === undefined) {
6!
83
            throw new Error("Connection strings not available");
×
84
        }
×
85
        const connectionString = getConnectionString(cluster.connectionStrings, connectionType);
6✔
86
        if (connectionString === undefined) {
6!
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)}`;
6✔
93
        const password = await generateSecurePassword();
6✔
94

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

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

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

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

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

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

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

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

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

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

159
                lastError = error;
2✔
160

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

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

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

179
        if (lastError) {
6!
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({
5✔
208
            id: LogId.atlasConnectSucceeded,
5✔
209
            context: "atlas-connect-cluster",
5✔
210
            message: `connected to cluster: ${this.session.connectedAtlasCluster?.clusterName}`,
6✔
211
            noRedaction: true,
6✔
212
        });
6✔
213
    }
6✔
214

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

223
        for (let i = 0; i < 60; i++) {
1✔
224
            const state = this.queryConnection(projectId, clusterName);
7✔
225
            switch (state) {
7✔
226
                case "connected": {
7✔
227
                    const content: CallToolResult["content"] = [
1✔
228
                        {
1✔
229
                            type: "text",
1✔
230
                            text: `Connected to cluster "${clusterName}".`,
1✔
231
                        },
1✔
232
                    ];
1✔
233

234
                    if (ipAccessListUpdated) {
1✔
235
                        content.push({
1✔
236
                            type: "text",
1✔
237
                            text: addedIpAccessListMessage,
1✔
238
                        });
1✔
239
                    }
1✔
240

241
                    if (createdUser) {
1✔
242
                        content.push({
1✔
243
                            type: "text",
1✔
244
                            text: createdUserMessage,
1✔
245
                        });
1✔
246
                    }
1✔
247

248
                    return { content };
1✔
249
                }
1✔
250
                case "connecting":
7!
251
                case "unknown": {
7!
UNCOV
252
                    break;
×
UNCOV
253
                }
×
254
                case "connected-to-other-cluster":
7!
255
                case "disconnected":
7✔
256
                default: {
7✔
257
                    await this.session.disconnect();
6✔
258
                    const { connectionString, atlas } = await this.prepareClusterConnection(
6✔
259
                        projectId,
6✔
260
                        clusterName,
6✔
261
                        connectionType
6✔
262
                    );
6✔
263

264
                    createdUser = true;
6✔
265
                    // try to connect for about 5 minutes asynchronously
266
                    void this.connectToCluster(connectionString, atlas).catch((err: unknown) => {
6✔
267
                        const error = err instanceof Error ? err : new Error(String(err));
×
268
                        this.session.logger.error({
×
269
                            id: LogId.atlasConnectFailure,
×
270
                            context: "atlas-connect-cluster",
×
271
                            message: `error connecting to cluster: ${error.message}`,
×
272
                        });
×
273
                    });
6✔
274
                    break;
6✔
275
                }
6✔
276
            }
7✔
277

278
            await sleep(500);
6✔
279
        }
6✔
280

281
        const content: CallToolResult["content"] = [
6✔
282
            {
6✔
283
                type: "text" as const,
6✔
284
                text: `Attempting to connect to cluster "${clusterName}"...`,
6✔
285
            },
6✔
286
            {
6✔
287
                type: "text" as const,
6✔
288
                text: `Warning: Provisioning a user and connecting to the cluster may take more time, please check again in a few seconds.`,
6✔
289
            },
6✔
290
        ];
6✔
291

292
        if (ipAccessListUpdated) {
1!
293
            content.push({
×
294
                type: "text" as const,
×
295
                text: addedIpAccessListMessage,
×
296
            });
×
297
        }
×
298

299
        if (createdUser) {
×
300
            content.push({
×
301
                type: "text" as const,
×
302
                text: createdUserMessage,
×
303
            });
×
304
        }
×
305

306
        return { content };
×
307
    }
1✔
308

309
    protected override resolveTelemetryMetadata(
3✔
NEW
310
        result: CallToolResult,
×
NEW
311
        args: Parameters<ToolCallback<typeof this.argsShape>>
×
NEW
312
    ): ConnectionMetadata {
×
NEW
313
        const parentMetadata = super.resolveTelemetryMetadata(result, ...args);
×
NEW
314
        const connectionMetadata = this.getConnectionInfoMetadata();
×
315
        // Explicitly merge, preferring parentMetadata for known overlapping keys (project_id, org_id)
316
        // since parent has more complete information from tool arguments
NEW
317
        const { project_id, org_id, ...restConnectionMetadata } = connectionMetadata;
×
NEW
318
        const finalProjectId = parentMetadata.project_id ?? project_id;
×
NEW
319
        const finalOrgId = parentMetadata.org_id ?? org_id;
×
NEW
320
        return {
×
NEW
321
            ...parentMetadata,
×
NEW
322
            ...restConnectionMetadata,
×
323
            // Only include project_id and org_id if they are defined
NEW
324
            ...(finalProjectId !== undefined && { project_id: finalProjectId }),
×
NEW
325
            ...(finalOrgId !== undefined && { org_id: finalOrgId }),
×
NEW
326
        };
×
NEW
327
    }
×
328
}
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