Skip to content

Commit aea39a9

Browse files
committed
fix: delete currently connected user
1 parent 6f2ab22 commit aea39a9

File tree

5 files changed

+81
-30
lines changed

5 files changed

+81
-30
lines changed

src/common/utils.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.

src/errors.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
export enum ErrorCodes {
22
NotConnectedToMongoDB = 1_000_000,
33
InvalidParams = 1_000_001,
4+
CloseServiceProvider = 1_000_007,
5+
DeleteDatabaseUser = 1_000_008,
46
}
57

68
export class MongoDBError extends Error {

src/session.ts

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver";
22
import { ApiClient, ApiClientCredentials } from "./common/atlas/apiClient.js";
33
import { Implementation } from "@modelcontextprotocol/sdk/types.js";
4+
import logger from "./logger.js";
5+
import { mongoLogId } from "mongodb-log-writer";
6+
import { ErrorCodes } from "./errors.js";
47

58
export interface SessionOptions {
69
apiBaseUrl?: string;
@@ -16,6 +19,12 @@ export class Session {
1619
name: string;
1720
version: string;
1821
};
22+
connectedAtlasCluster?: {
23+
username: string;
24+
projectId: string;
25+
clusterName: string;
26+
expiryDate: Date;
27+
};
1928

2029
constructor({ apiBaseUrl, apiClientId, apiClientSecret }: SessionOptions = {}) {
2130
const credentials: ApiClientCredentials | undefined =
@@ -41,14 +50,46 @@ export class Session {
4150
}
4251
}
4352

44-
async close(): Promise<void> {
53+
async disconnect() {
4554
if (this.serviceProvider) {
4655
try {
4756
await this.serviceProvider.close(true);
48-
} catch (error) {
49-
console.error("Error closing service provider:", error);
57+
} catch (err: unknown) {
58+
const error = err instanceof Error ? err : new Error(String(err));
59+
logger.error(
60+
mongoLogId(ErrorCodes.CloseServiceProvider),
61+
"Error closing service provider:",
62+
error.message
63+
);
5064
}
5165
this.serviceProvider = undefined;
5266
}
67+
if (!this.connectedAtlasCluster) {
68+
return;
69+
}
70+
try {
71+
await this.apiClient.deleteDatabaseUser({
72+
params: {
73+
path: {
74+
groupId: this.connectedAtlasCluster.projectId,
75+
username: this.connectedAtlasCluster.username,
76+
databaseName: "admin",
77+
},
78+
},
79+
});
80+
} catch (err: unknown) {
81+
const error = err instanceof Error ? err : new Error(String(err));
82+
83+
logger.error(
84+
mongoLogId(ErrorCodes.DeleteDatabaseUser),
85+
"atlas-connect-cluster",
86+
`Error deleting previous database user: ${error.message}`
87+
);
88+
}
89+
this.connectedAtlasCluster = undefined;
90+
}
91+
92+
async close(): Promise<void> {
93+
await this.disconnect();
5394
}
5495
}

src/tools/atlas/metadata/connectCluster.ts

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@ import { z } from "zod";
22
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
33
import { AtlasToolBase } from "../atlasTool.js";
44
import { ToolArgs, OperationType } from "../../tool.js";
5-
import { sleep } from "../../../common/utils.js";
5+
import { randomBytes } from "crypto";
6+
import { promisify } from "util";
67

7-
function generateSecurePassword(): string {
8-
// TODO: use a better password generator
9-
return `pwdMcp${Math.floor(Math.random() * 100000)}`;
8+
const EXPIRY_MS = 1000 * 60 * 60 * 12; // 12 hours
9+
10+
const randomBytesAsync = promisify(randomBytes);
11+
12+
async function generateSecurePassword(): Promise<string> {
13+
const buf = await randomBytesAsync(16);
14+
const pass = buf.toString("base64url");
15+
return pass;
1016
}
1117

1218
export class ConnectClusterTool extends AtlasToolBase {
@@ -19,6 +25,8 @@ export class ConnectClusterTool extends AtlasToolBase {
1925
};
2026

2127
protected async execute({ projectId, clusterName }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
28+
await this.session.disconnect();
29+
2230
const cluster = await this.session.apiClient.getCluster({
2331
params: {
2432
path: {
@@ -32,15 +40,16 @@ export class ConnectClusterTool extends AtlasToolBase {
3240
throw new Error("Cluster not found");
3341
}
3442

35-
if (!cluster.connectionStrings?.standardSrv || !cluster.connectionStrings?.standard) {
43+
const baseConnectionString = cluster.connectionStrings?.standardSrv || cluster.connectionStrings?.standard;
44+
45+
if (!baseConnectionString) {
3646
throw new Error("Connection string not available");
3747
}
3848

3949
const username = `usrMcp${Math.floor(Math.random() * 100000)}`;
40-
const password = generateSecurePassword();
50+
const password = await generateSecurePassword();
4151

42-
const expiryMs = 1000 * 60 * 60 * 12; // 12 hours
43-
const expiryDate = new Date(Date.now() + expiryMs);
52+
const expiryDate = new Date(Date.now() + EXPIRY_MS);
4453

4554
await this.session.apiClient.createDatabaseUser({
4655
params: {
@@ -68,19 +77,18 @@ export class ConnectClusterTool extends AtlasToolBase {
6877
},
6978
});
7079

71-
void sleep(expiryMs).then(async () => {
72-
// disconnect after 12 hours
73-
if (this.session.serviceProvider) {
74-
await this.session.serviceProvider.close(true);
75-
this.session.serviceProvider = undefined;
76-
}
77-
});
80+
this.session.connectedAtlasCluster = {
81+
username,
82+
projectId,
83+
clusterName,
84+
expiryDate,
85+
};
7886

79-
const connectionString =
80-
(cluster.connectionStrings.standardSrv || cluster.connectionStrings.standard || "").replace(
81-
"://",
82-
`://${username}:${password}@`
83-
) + `?authSource=admin`;
87+
const cn = new URL(baseConnectionString);
88+
cn.username = username;
89+
cn.password = password;
90+
cn.searchParams.set("authSource", "admin");
91+
const connectionString = cn.toString();
8492

8593
await this.connectToMongoDB(connectionString);
8694

tests/integration/tools/atlas/clusters.test.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@ import { Session } from "../../../../src/session.js";
22
import { expectDefined } from "../../helpers.js";
33
import { describeWithAtlas, withProject, randomId } from "./atlasHelpers.js";
44
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
5-
import { sleep } from "../../../../src/common/utils.js";
5+
6+
function sleep(ms: number) {
7+
return new Promise((resolve) => setTimeout(resolve, ms));
8+
}
69

710
async function deleteAndWaitCluster(session: Session, projectId: string, clusterName: string) {
811
await session.apiClient.deleteCluster({
912
params: {
1013
path: {
1114
groupId: projectId,
12-
clusterName: clusterName,
15+
clusterName,
1316
},
1417
},
1518
});
@@ -19,7 +22,7 @@ async function deleteAndWaitCluster(session: Session, projectId: string, cluster
1922
params: {
2023
path: {
2124
groupId: projectId,
22-
clusterName: clusterName,
25+
clusterName,
2326
},
2427
},
2528
});
@@ -36,7 +39,7 @@ async function waitClusterState(session: Session, projectId: string, clusterName
3639
params: {
3740
path: {
3841
groupId: projectId,
39-
clusterName: clusterName,
42+
clusterName,
4043
},
4144
},
4245
});

0 commit comments

Comments
 (0)