From c4f2d5a5aab727da6160544f8b4fb251df29d741 Mon Sep 17 00:00:00 2001 From: Bianca Lisle Date: Mon, 1 Sep 2025 17:45:50 +0100 Subject: [PATCH 1/7] feat: add more details about atlas connect flow --- src/tools/atlas/connect/connectCluster.ts | 80 +++++++++++++++-------- 1 file changed, 52 insertions(+), 28 deletions(-) diff --git a/src/tools/atlas/connect/connectCluster.ts b/src/tools/atlas/connect/connectCluster.ts index 9695ff369..9975074df 100644 --- a/src/tools/atlas/connect/connectCluster.ts +++ b/src/tools/atlas/connect/connectCluster.ts @@ -62,7 +62,7 @@ export class ConnectClusterTool extends AtlasToolBase { private async prepareClusterConnection( projectId: string, clusterName: string - ): Promise<{ connectionString: string; atlas: AtlasClusterConnectionInfo }> { + ): Promise<{ connectionString: string; atlas: AtlasClusterConnectionInfo; userCreated: boolean }> { const cluster = await inspectCluster(this.session.apiClient, projectId, clusterName); if (!cluster.connectionString) { @@ -110,7 +110,7 @@ export class ConnectClusterTool extends AtlasToolBase { cn.password = password; cn.searchParams.set("authSource", "admin"); - return { connectionString: cn.toString(), atlas: connectedAtlasCluster }; + return { connectionString: cn.toString(), atlas: connectedAtlasCluster, userCreated: true }; } private async connectToCluster(connectionString: string, atlas: AtlasClusterConnectionInfo): Promise { @@ -190,19 +190,29 @@ export class ConnectClusterTool extends AtlasToolBase { } protected async execute({ projectId, clusterName }: ToolArgs): Promise { - await ensureCurrentIpInAccessList(this.session.apiClient, projectId); + const ipAccessListUpdated = await ensureCurrentIpInAccessList(this.session.apiClient, projectId); + let createdUser = false; + for (let i = 0; i < 60; i++) { const state = this.queryConnection(projectId, clusterName); switch (state) { case "connected": { - return { - content: [ - { - type: "text", - text: `Connected to cluster "${clusterName}".`, - }, - ], - }; + const content: CallToolResult["content"] = [ + { + type: "text", + text: `Connected to cluster "${clusterName}".`, + }, + ]; + + // Add feedback about IP access list if it was updated + if (ipAccessListUpdated) { + content.push({ + type: "text", + text: `Note: Your current IP address has been added to the Atlas project's IP access list to enable secure connection.`, + }); + } + + return { content }; } case "connecting": case "unknown": { @@ -212,8 +222,12 @@ export class ConnectClusterTool extends AtlasToolBase { case "disconnected": default: { await this.session.disconnect(); - const { connectionString, atlas } = await this.prepareClusterConnection(projectId, clusterName); + const { connectionString, atlas, userCreated } = await this.prepareClusterConnection( + projectId, + clusterName + ); + createdUser = userCreated; // try to connect for about 5 minutes asynchronously void this.connectToCluster(connectionString, atlas).catch((err: unknown) => { const error = err instanceof Error ? err : new Error(String(err)); @@ -230,21 +244,31 @@ export class ConnectClusterTool extends AtlasToolBase { await sleep(500); } - return { - content: [ - { - type: "text" as const, - text: `Attempting to connect to cluster "${clusterName}"...`, - }, - { - type: "text" as const, - text: `Warning: Provisioning a user and connecting to the cluster may take more time, please check again in a few seconds.`, - }, - { - type: "text" as const, - text: `Warning: Make sure your IP address was enabled in the allow list setting of the Atlas cluster.`, - }, - ], - }; + const content: CallToolResult["content"] = [ + { + type: "text" as const, + text: `Attempting to connect to cluster "${clusterName}"...`, + }, + { + type: "text" as const, + text: `Warning: Provisioning a user and connecting to the cluster may take more time, please check again in a few seconds.`, + }, + ]; + + if (ipAccessListUpdated) { + content.push({ + type: "text" as const, + text: `Note: Your current IP address has been added to the Atlas project's IP access list to enable secure connection.`, + }); + } + + if (createdUser) { + content.push({ + type: "text" as const, + text: `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`, + }); + } + + return { content }; } } From 114c7457fd186c9b8f37e7ee381a336fa1b8c370 Mon Sep 17 00:00:00 2001 From: Bianca Lisle Date: Mon, 1 Sep 2025 17:51:28 +0100 Subject: [PATCH 2/7] fix: add missing file changes --- src/common/atlas/accessListUtils.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/common/atlas/accessListUtils.ts b/src/common/atlas/accessListUtils.ts index dbc53df66..34609c3a3 100644 --- a/src/common/atlas/accessListUtils.ts +++ b/src/common/atlas/accessListUtils.ts @@ -22,8 +22,9 @@ export async function makeCurrentIpAccessListEntry( * If the IP is already present, this is a no-op. * @param apiClient The Atlas API client instance * @param projectId The Atlas project ID + * @returns Promise - true if a new IP access list entry was created, false if it already existed */ -export async function ensureCurrentIpInAccessList(apiClient: ApiClient, projectId: string): Promise { +export async function ensureCurrentIpInAccessList(apiClient: ApiClient, projectId: string): Promise { const entry = await makeCurrentIpAccessListEntry(apiClient, projectId, DEFAULT_ACCESS_LIST_COMMENT); try { await apiClient.createProjectIpAccessList({ @@ -35,6 +36,7 @@ export async function ensureCurrentIpInAccessList(apiClient: ApiClient, projectI context: "accessListUtils", message: `IP access list created: ${JSON.stringify(entry)}`, }); + return true; } catch (err) { if (err instanceof ApiClientError && err.response?.status === 409) { // 409 Conflict: entry already exists, log info @@ -43,7 +45,7 @@ export async function ensureCurrentIpInAccessList(apiClient: ApiClient, projectI context: "accessListUtils", message: `IP address ${entry.ipAddress} is already present in the access list for project ${projectId}.`, }); - return; + return false; } apiClient.logger.warning({ id: LogId.atlasIpAccessListAddFailure, @@ -51,4 +53,5 @@ export async function ensureCurrentIpInAccessList(apiClient: ApiClient, projectI message: `Error adding IP access list: ${err instanceof Error ? err.message : String(err)}`, }); } + return false; } From 9ff88ed22b841046b193a91d57870af8f7a13712 Mon Sep 17 00:00:00 2001 From: Bianca Lisle Date: Mon, 1 Sep 2025 17:59:09 +0100 Subject: [PATCH 3/7] address copilot comment --- src/tools/atlas/connect/connectCluster.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tools/atlas/connect/connectCluster.ts b/src/tools/atlas/connect/connectCluster.ts index 9975074df..eb59a97cd 100644 --- a/src/tools/atlas/connect/connectCluster.ts +++ b/src/tools/atlas/connect/connectCluster.ts @@ -10,6 +10,8 @@ import type { AtlasClusterConnectionInfo } from "../../../common/connectionManag import { getDefaultRoleFromConfig } from "../../../common/atlas/roles.js"; const EXPIRY_MS = 1000 * 60 * 60 * 12; // 12 hours +const addedIpAccessListMessage = + "Note: Your current IP address has been added to the Atlas project's IP access list to enable secure connection."; function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); @@ -208,7 +210,7 @@ export class ConnectClusterTool extends AtlasToolBase { if (ipAccessListUpdated) { content.push({ type: "text", - text: `Note: Your current IP address has been added to the Atlas project's IP access list to enable secure connection.`, + text: addedIpAccessListMessage, }); } @@ -258,7 +260,7 @@ export class ConnectClusterTool extends AtlasToolBase { if (ipAccessListUpdated) { content.push({ type: "text" as const, - text: `Note: Your current IP address has been added to the Atlas project's IP access list to enable secure connection.`, + text: addedIpAccessListMessage, }); } From 8aeee070283d3719cb0010f4c8373bc33a3c79b9 Mon Sep 17 00:00:00 2001 From: Bianca Lisle Date: Mon, 1 Sep 2025 20:11:53 +0100 Subject: [PATCH 4/7] update tests --- src/tools/atlas/connect/connectCluster.ts | 13 ++++++++-- tests/integration/tools/atlas/atlasHelpers.ts | 5 ++++ .../integration/tools/atlas/clusters.test.ts | 25 +++++++++++++------ 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/tools/atlas/connect/connectCluster.ts b/src/tools/atlas/connect/connectCluster.ts index eb59a97cd..a2fdf1953 100644 --- a/src/tools/atlas/connect/connectCluster.ts +++ b/src/tools/atlas/connect/connectCluster.ts @@ -13,6 +13,9 @@ const EXPIRY_MS = 1000 * 60 * 60 * 12; // 12 hours const addedIpAccessListMessage = "Note: Your current IP address has been added to the Atlas project's IP access list to enable secure connection."; +const createdUserMessage = + "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"; + function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } @@ -206,7 +209,6 @@ export class ConnectClusterTool extends AtlasToolBase { }, ]; - // Add feedback about IP access list if it was updated if (ipAccessListUpdated) { content.push({ type: "text", @@ -214,6 +216,13 @@ export class ConnectClusterTool extends AtlasToolBase { }); } + if (createdUser) { + content.push({ + type: "text", + text: createdUserMessage, + }); + } + return { content }; } case "connecting": @@ -267,7 +276,7 @@ export class ConnectClusterTool extends AtlasToolBase { if (createdUser) { content.push({ type: "text" as const, - text: `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`, + text: createdUserMessage, }); } diff --git a/tests/integration/tools/atlas/atlasHelpers.ts b/tests/integration/tools/atlas/atlasHelpers.ts index 7de5966f6..00ac53feb 100644 --- a/tests/integration/tools/atlas/atlasHelpers.ts +++ b/tests/integration/tools/atlas/atlasHelpers.ts @@ -29,6 +29,7 @@ export function describeWithAtlas(name: string, fn: IntegrationTestFunction): vo interface ProjectTestArgs { getProjectId: () => string; + getIpAddress: () => string; } type ProjectTestFunction = (args: ProjectTestArgs) => void; @@ -36,6 +37,7 @@ type ProjectTestFunction = (args: ProjectTestArgs) => void; export function withProject(integration: IntegrationTest, fn: ProjectTestFunction): SuiteCollector { return describe("with project", () => { let projectId: string = ""; + let ipAddress: string = ""; beforeAll(async () => { const apiClient = integration.mcpServer().session.apiClient; @@ -49,6 +51,8 @@ export function withProject(integration: IntegrationTest, fn: ProjectTestFunctio await apiClient.validateAccessToken(); try { const group = await createProject(apiClient); + const ipInfo = await apiClient.getIpInfo(); + ipAddress = ipInfo.currentIpv4Address; projectId = group.id; } catch (error) { console.error("Failed to create project:", error); @@ -72,6 +76,7 @@ export function withProject(integration: IntegrationTest, fn: ProjectTestFunctio const args = { getProjectId: (): string => projectId, + getIpAddress: (): string => ipAddress, }; fn(args); diff --git a/tests/integration/tools/atlas/clusters.test.ts b/tests/integration/tools/atlas/clusters.test.ts index 9ae7aabcb..32173c14c 100644 --- a/tests/integration/tools/atlas/clusters.test.ts +++ b/tests/integration/tools/atlas/clusters.test.ts @@ -57,7 +57,7 @@ async function waitCluster( } describeWithAtlas("clusters", (integration) => { - withProject(integration, ({ getProjectId }) => { + withProject(integration, ({ getProjectId, getIpAddress }) => { const clusterName = "ClusterTest-" + randomId; afterAll(async () => { @@ -162,6 +162,7 @@ describeWithAtlas("clusters", (integration) => { describe("atlas-connect-cluster", () => { beforeAll(async () => { const projectId = getProjectId(); + const ipAddress = getIpAddress(); await waitCluster(integration.mcpServer().session, projectId, clusterName, (cluster) => { return ( cluster.stateName === "IDLE" && @@ -177,7 +178,7 @@ describeWithAtlas("clusters", (integration) => { body: [ { comment: "MCP test", - cidrBlock: "0.0.0.0/0", + ipAddress: ipAddress, }, ], }); @@ -196,6 +197,7 @@ describeWithAtlas("clusters", (integration) => { it("connects to cluster", async () => { const projectId = getProjectId(); + let connected = false; for (let i = 0; i < 10; i++) { const response = await integration.mcpClient().callTool({ @@ -205,16 +207,25 @@ describeWithAtlas("clusters", (integration) => { const elements = getResponseElements(response.content); expect(elements.length).toBeGreaterThanOrEqual(1); - if ( - elements[0]?.text.includes("Cluster is already connected.") || - elements[0]?.text.includes(`Connected to cluster "${clusterName}"`) - ) { - break; // success + if (elements[0]?.text.includes(`Connected to cluster "${clusterName}"`)) { + connected = true; + + // assert that some of the element s have the message + expect( + elements.some((element) => + element.text.includes( + "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" + ) + ) + ).toBe(true); + + break; } else { expect(elements[0]?.text).toContain(`Attempting to connect to cluster "${clusterName}"...`); } await sleep(500); } + expect(connected).toBe(true); }); describe("when not connected", () => { From bba7218dc8a9e9287c226a82095ed2cc453cbc2e Mon Sep 17 00:00:00 2001 From: Bianca Lisle Date: Mon, 1 Sep 2025 20:12:58 +0100 Subject: [PATCH 5/7] update test --- tests/integration/tools/atlas/clusters.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/integration/tools/atlas/clusters.test.ts b/tests/integration/tools/atlas/clusters.test.ts index 32173c14c..ccb6e774d 100644 --- a/tests/integration/tools/atlas/clusters.test.ts +++ b/tests/integration/tools/atlas/clusters.test.ts @@ -84,7 +84,6 @@ describeWithAtlas("clusters", (integration) => { it("should create a free cluster and add current IP to access list", async () => { const projectId = getProjectId(); const session = integration.mcpServer().session; - const ipInfo = await session.apiClient.getIpInfo(); const response = await integration.mcpClient().callTool({ name: "atlas-create-free-cluster", @@ -102,7 +101,7 @@ describeWithAtlas("clusters", (integration) => { const accessList = await session.apiClient.listProjectIpAccessLists({ params: { path: { groupId: projectId } }, }); - const found = accessList.results?.some((entry) => entry.ipAddress === ipInfo.currentIpv4Address); + const found = accessList.results?.some((entry) => entry.ipAddress === getIpAddress()); expect(found).toBe(true); }); }); From c2edf9f2657d689b180ca9c149016dbbf9d7f104 Mon Sep 17 00:00:00 2001 From: Bianca Lisle Date: Wed, 3 Sep 2025 13:43:32 +0100 Subject: [PATCH 6/7] address comment: improve LLM instruction --- src/tools/atlas/connect/connectCluster.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/atlas/connect/connectCluster.ts b/src/tools/atlas/connect/connectCluster.ts index a2fdf1953..11fb9051a 100644 --- a/src/tools/atlas/connect/connectCluster.ts +++ b/src/tools/atlas/connect/connectCluster.ts @@ -14,7 +14,7 @@ const addedIpAccessListMessage = "Note: Your current IP address has been added to the Atlas project's IP access list to enable secure connection."; const createdUserMessage = - "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"; + "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"; function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); From c6094340af762ff8a31974bd35d3ea92cd3a2888 Mon Sep 17 00:00:00 2001 From: Bianca Lisle Date: Wed, 3 Sep 2025 13:46:38 +0100 Subject: [PATCH 7/7] address comment: remove createdCluster from prepareClusterConenection --- src/tools/atlas/connect/connectCluster.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/tools/atlas/connect/connectCluster.ts b/src/tools/atlas/connect/connectCluster.ts index 11fb9051a..13f55acec 100644 --- a/src/tools/atlas/connect/connectCluster.ts +++ b/src/tools/atlas/connect/connectCluster.ts @@ -67,7 +67,7 @@ export class ConnectClusterTool extends AtlasToolBase { private async prepareClusterConnection( projectId: string, clusterName: string - ): Promise<{ connectionString: string; atlas: AtlasClusterConnectionInfo; userCreated: boolean }> { + ): Promise<{ connectionString: string; atlas: AtlasClusterConnectionInfo }> { const cluster = await inspectCluster(this.session.apiClient, projectId, clusterName); if (!cluster.connectionString) { @@ -115,7 +115,7 @@ export class ConnectClusterTool extends AtlasToolBase { cn.password = password; cn.searchParams.set("authSource", "admin"); - return { connectionString: cn.toString(), atlas: connectedAtlasCluster, userCreated: true }; + return { connectionString: cn.toString(), atlas: connectedAtlasCluster }; } private async connectToCluster(connectionString: string, atlas: AtlasClusterConnectionInfo): Promise { @@ -233,12 +233,9 @@ export class ConnectClusterTool extends AtlasToolBase { case "disconnected": default: { await this.session.disconnect(); - const { connectionString, atlas, userCreated } = await this.prepareClusterConnection( - projectId, - clusterName - ); + const { connectionString, atlas } = await this.prepareClusterConnection(projectId, clusterName); - createdUser = userCreated; + createdUser = true; // try to connect for about 5 minutes asynchronously void this.connectToCluster(connectionString, atlas).catch((err: unknown) => { const error = err instanceof Error ? err : new Error(String(err));