Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 345efa4

Browse files
authored
feat: add more details about atlas connect flow - MCP-124 (#500)
1 parent 94dfc08 commit 345efa4

File tree

4 files changed

+86
-36
lines changed

4 files changed

+86
-36
lines changed

src/common/atlas/accessListUtils.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ export async function makeCurrentIpAccessListEntry(
2222
* If the IP is already present, this is a no-op.
2323
* @param apiClient The Atlas API client instance
2424
* @param projectId The Atlas project ID
25+
* @returns Promise<boolean> - true if a new IP access list entry was created, false if it already existed
2526
*/
26-
export async function ensureCurrentIpInAccessList(apiClient: ApiClient, projectId: string): Promise<void> {
27+
export async function ensureCurrentIpInAccessList(apiClient: ApiClient, projectId: string): Promise<boolean> {
2728
const entry = await makeCurrentIpAccessListEntry(apiClient, projectId, DEFAULT_ACCESS_LIST_COMMENT);
2829
try {
2930
await apiClient.createProjectIpAccessList({
@@ -35,6 +36,7 @@ export async function ensureCurrentIpInAccessList(apiClient: ApiClient, projectI
3536
context: "accessListUtils",
3637
message: `IP access list created: ${JSON.stringify(entry)}`,
3738
});
39+
return true;
3840
} catch (err) {
3941
if (err instanceof ApiClientError && err.response?.status === 409) {
4042
// 409 Conflict: entry already exists, log info
@@ -43,12 +45,13 @@ export async function ensureCurrentIpInAccessList(apiClient: ApiClient, projectI
4345
context: "accessListUtils",
4446
message: `IP address ${entry.ipAddress} is already present in the access list for project ${projectId}.`,
4547
});
46-
return;
48+
return false;
4749
}
4850
apiClient.logger.warning({
4951
id: LogId.atlasIpAccessListAddFailure,
5052
context: "accessListUtils",
5153
message: `Error adding IP access list: ${err instanceof Error ? err.message : String(err)}`,
5254
});
5355
}
56+
return false;
5457
}

src/tools/atlas/connect/connectCluster.ts

Lines changed: 57 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ import type { AtlasClusterConnectionInfo } from "../../../common/connectionManag
1010
import { getDefaultRoleFromConfig } from "../../../common/atlas/roles.js";
1111

1212
const EXPIRY_MS = 1000 * 60 * 60 * 12; // 12 hours
13+
const addedIpAccessListMessage =
14+
"Note: Your current IP address has been added to the Atlas project's IP access list to enable secure connection.";
15+
16+
const createdUserMessage =
17+
"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";
1318

1419
function sleep(ms: number): Promise<void> {
1520
return new Promise((resolve) => setTimeout(resolve, ms));
@@ -190,19 +195,35 @@ export class ConnectClusterTool extends AtlasToolBase {
190195
}
191196

192197
protected async execute({ projectId, clusterName }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
193-
await ensureCurrentIpInAccessList(this.session.apiClient, projectId);
198+
const ipAccessListUpdated = await ensureCurrentIpInAccessList(this.session.apiClient, projectId);
199+
let createdUser = false;
200+
194201
for (let i = 0; i < 60; i++) {
195202
const state = this.queryConnection(projectId, clusterName);
196203
switch (state) {
197204
case "connected": {
198-
return {
199-
content: [
200-
{
201-
type: "text",
202-
text: `Connected to cluster "${clusterName}".`,
203-
},
204-
],
205-
};
205+
const content: CallToolResult["content"] = [
206+
{
207+
type: "text",
208+
text: `Connected to cluster "${clusterName}".`,
209+
},
210+
];
211+
212+
if (ipAccessListUpdated) {
213+
content.push({
214+
type: "text",
215+
text: addedIpAccessListMessage,
216+
});
217+
}
218+
219+
if (createdUser) {
220+
content.push({
221+
type: "text",
222+
text: createdUserMessage,
223+
});
224+
}
225+
226+
return { content };
206227
}
207228
case "connecting":
208229
case "unknown": {
@@ -214,6 +235,7 @@ export class ConnectClusterTool extends AtlasToolBase {
214235
await this.session.disconnect();
215236
const { connectionString, atlas } = await this.prepareClusterConnection(projectId, clusterName);
216237

238+
createdUser = true;
217239
// try to connect for about 5 minutes asynchronously
218240
void this.connectToCluster(connectionString, atlas).catch((err: unknown) => {
219241
const error = err instanceof Error ? err : new Error(String(err));
@@ -230,21 +252,31 @@ export class ConnectClusterTool extends AtlasToolBase {
230252
await sleep(500);
231253
}
232254

233-
return {
234-
content: [
235-
{
236-
type: "text" as const,
237-
text: `Attempting to connect to cluster "${clusterName}"...`,
238-
},
239-
{
240-
type: "text" as const,
241-
text: `Warning: Provisioning a user and connecting to the cluster may take more time, please check again in a few seconds.`,
242-
},
243-
{
244-
type: "text" as const,
245-
text: `Warning: Make sure your IP address was enabled in the allow list setting of the Atlas cluster.`,
246-
},
247-
],
248-
};
255+
const content: CallToolResult["content"] = [
256+
{
257+
type: "text" as const,
258+
text: `Attempting to connect to cluster "${clusterName}"...`,
259+
},
260+
{
261+
type: "text" as const,
262+
text: `Warning: Provisioning a user and connecting to the cluster may take more time, please check again in a few seconds.`,
263+
},
264+
];
265+
266+
if (ipAccessListUpdated) {
267+
content.push({
268+
type: "text" as const,
269+
text: addedIpAccessListMessage,
270+
});
271+
}
272+
273+
if (createdUser) {
274+
content.push({
275+
type: "text" as const,
276+
text: createdUserMessage,
277+
});
278+
}
279+
280+
return { content };
249281
}
250282
}

tests/integration/tools/atlas/atlasHelpers.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,15 @@ export function describeWithAtlas(name: string, fn: IntegrationTestFunction): vo
2929

3030
interface ProjectTestArgs {
3131
getProjectId: () => string;
32+
getIpAddress: () => string;
3233
}
3334

3435
type ProjectTestFunction = (args: ProjectTestArgs) => void;
3536

3637
export function withProject(integration: IntegrationTest, fn: ProjectTestFunction): SuiteCollector<object> {
3738
return describe("with project", () => {
3839
let projectId: string = "";
40+
let ipAddress: string = "";
3941

4042
beforeAll(async () => {
4143
const apiClient = integration.mcpServer().session.apiClient;
@@ -49,6 +51,8 @@ export function withProject(integration: IntegrationTest, fn: ProjectTestFunctio
4951
await apiClient.validateAccessToken();
5052
try {
5153
const group = await createProject(apiClient);
54+
const ipInfo = await apiClient.getIpInfo();
55+
ipAddress = ipInfo.currentIpv4Address;
5256
projectId = group.id;
5357
} catch (error) {
5458
console.error("Failed to create project:", error);
@@ -72,6 +76,7 @@ export function withProject(integration: IntegrationTest, fn: ProjectTestFunctio
7276

7377
const args = {
7478
getProjectId: (): string => projectId,
79+
getIpAddress: (): string => ipAddress,
7580
};
7681

7782
fn(args);

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

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ async function waitCluster(
5757
}
5858

5959
describeWithAtlas("clusters", (integration) => {
60-
withProject(integration, ({ getProjectId }) => {
60+
withProject(integration, ({ getProjectId, getIpAddress }) => {
6161
const clusterName = "ClusterTest-" + randomId;
6262

6363
afterAll(async () => {
@@ -84,7 +84,6 @@ describeWithAtlas("clusters", (integration) => {
8484
it("should create a free cluster and add current IP to access list", async () => {
8585
const projectId = getProjectId();
8686
const session = integration.mcpServer().session;
87-
const ipInfo = await session.apiClient.getIpInfo();
8887

8988
const response = await integration.mcpClient().callTool({
9089
name: "atlas-create-free-cluster",
@@ -102,7 +101,7 @@ describeWithAtlas("clusters", (integration) => {
102101
const accessList = await session.apiClient.listProjectIpAccessLists({
103102
params: { path: { groupId: projectId } },
104103
});
105-
const found = accessList.results?.some((entry) => entry.ipAddress === ipInfo.currentIpv4Address);
104+
const found = accessList.results?.some((entry) => entry.ipAddress === getIpAddress());
106105
expect(found).toBe(true);
107106
});
108107
});
@@ -162,6 +161,7 @@ describeWithAtlas("clusters", (integration) => {
162161
describe("atlas-connect-cluster", () => {
163162
beforeAll(async () => {
164163
const projectId = getProjectId();
164+
const ipAddress = getIpAddress();
165165
await waitCluster(integration.mcpServer().session, projectId, clusterName, (cluster) => {
166166
return (
167167
cluster.stateName === "IDLE" &&
@@ -177,7 +177,7 @@ describeWithAtlas("clusters", (integration) => {
177177
body: [
178178
{
179179
comment: "MCP test",
180-
cidrBlock: "0.0.0.0/0",
180+
ipAddress: ipAddress,
181181
},
182182
],
183183
});
@@ -196,6 +196,7 @@ describeWithAtlas("clusters", (integration) => {
196196

197197
it("connects to cluster", async () => {
198198
const projectId = getProjectId();
199+
let connected = false;
199200

200201
for (let i = 0; i < 10; i++) {
201202
const response = await integration.mcpClient().callTool({
@@ -205,16 +206,25 @@ describeWithAtlas("clusters", (integration) => {
205206

206207
const elements = getResponseElements(response.content);
207208
expect(elements.length).toBeGreaterThanOrEqual(1);
208-
if (
209-
elements[0]?.text.includes("Cluster is already connected.") ||
210-
elements[0]?.text.includes(`Connected to cluster "${clusterName}"`)
211-
) {
212-
break; // success
209+
if (elements[0]?.text.includes(`Connected to cluster "${clusterName}"`)) {
210+
connected = true;
211+
212+
// assert that some of the element s have the message
213+
expect(
214+
elements.some((element) =>
215+
element.text.includes(
216+
"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"
217+
)
218+
)
219+
).toBe(true);
220+
221+
break;
213222
} else {
214223
expect(elements[0]?.text).toContain(`Attempting to connect to cluster "${clusterName}"...`);
215224
}
216225
await sleep(500);
217226
}
227+
expect(connected).toBe(true);
218228
});
219229

220230
describe("when not connected", () => {

0 commit comments

Comments
 (0)