From 3f8a1b52a5b2c72d00c91ff8237c65b9dff59cd5 Mon Sep 17 00:00:00 2001 From: Bianca Lisle Date: Thu, 9 Oct 2025 12:43:36 +0100 Subject: [PATCH 1/3] feat: add support for private connection strings --- src/common/atlas/cluster.ts | 19 ++++++++++++++++- src/tools/args.ts | 6 ++++++ src/tools/atlas/connect/connectCluster.ts | 26 +++++++++++++++++------ 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/common/atlas/cluster.ts b/src/common/atlas/cluster.ts index 1ea30286..4ee8356f 100644 --- a/src/common/atlas/cluster.ts +++ b/src/common/atlas/cluster.ts @@ -1,4 +1,8 @@ -import type { ClusterDescription20240805, FlexClusterDescription20241113 } from "./openapi.js"; +import type { + ClusterConnectionStrings, + ClusterDescription20240805, + FlexClusterDescription20241113, +} from "./openapi.js"; import type { ApiClient } from "./apiClient.js"; import { LogId } from "../logger.js"; import { ConnectionString } from "mongodb-connection-string-url"; @@ -19,6 +23,7 @@ export interface Cluster { state?: "IDLE" | "CREATING" | "UPDATING" | "DELETING" | "REPAIRING"; mongoDBVersion?: string; connectionString?: string; + connectionStrings?: ClusterConnectionStrings; processIds?: Array; } @@ -31,6 +36,7 @@ export function formatFlexCluster(cluster: FlexClusterDescription20241113): Clus state: cluster.stateName, mongoDBVersion: cluster.mongoDBVersion, connectionString, + connectionStrings: cluster.connectionStrings, processIds: extractProcessIds(cluster.connectionStrings?.standard ?? ""), }; } @@ -74,10 +80,21 @@ export function formatCluster(cluster: ClusterDescription20240805): Cluster { state: cluster.stateName, mongoDBVersion: cluster.mongoDBVersion, connectionString, + connectionStrings: cluster.connectionStrings, processIds: extractProcessIds(cluster.connectionStrings?.standard ?? ""), }; } +export function getConnectionString( + connectionStrings: ClusterConnectionStrings, + connectionType: "standard" | "private" +): string | undefined { + if (connectionType === "standard") { + return connectionStrings.standardSrv || connectionStrings.standard; + } + return connectionStrings.privateSrv || connectionStrings.private; +} + export async function inspectCluster(apiClient: ApiClient, projectId: string, clusterName: string): Promise { try { const cluster = await apiClient.getCluster({ diff --git a/src/tools/args.ts b/src/tools/args.ts index 653f72da..a496de91 100644 --- a/src/tools/args.ts +++ b/src/tools/args.ts @@ -68,6 +68,12 @@ export const AtlasArgs = { password: (): z.ZodString => z.string().min(1, "Password is required").max(100, "Password must be 100 characters or less"), + + connectionType: (): z.ZodDefault> => + z + .enum(["standard", "private"]) + .default("standard") + .describe("Desired connection type (standard or private) to an Atlas cluster"), }; function toEJSON(value: T): T { diff --git a/src/tools/atlas/connect/connectCluster.ts b/src/tools/atlas/connect/connectCluster.ts index 54f3ae8b..6db29237 100644 --- a/src/tools/atlas/connect/connectCluster.ts +++ b/src/tools/atlas/connect/connectCluster.ts @@ -8,6 +8,7 @@ import { ensureCurrentIpInAccessList } from "../../../common/atlas/accessListUti import type { AtlasClusterConnectionInfo } from "../../../common/connectionManager.js"; import { getDefaultRoleFromConfig } from "../../../common/atlas/roles.js"; import { AtlasArgs } from "../../args.js"; +import { getConnectionString } from "../../../common/atlas/cluster.js"; const addedIpAccessListMessage = "Note: Your current IP address has been added to the Atlas project's IP access list to enable secure connection."; @@ -22,6 +23,7 @@ function sleep(ms: number): Promise { export const ConnectClusterArgs = { projectId: AtlasArgs.projectId().describe("Atlas project ID"), clusterName: AtlasArgs.clusterName().describe("Atlas cluster name"), + connectionType: AtlasArgs.connectionType().optional(), }; export class ConnectClusterTool extends AtlasToolBase { @@ -69,12 +71,16 @@ export class ConnectClusterTool extends AtlasToolBase { private async prepareClusterConnection( projectId: string, - clusterName: string + clusterName: string, + connectionType: "standard" | "private" ): Promise<{ connectionString: string; atlas: AtlasClusterConnectionInfo }> { const cluster = await inspectCluster(this.session.apiClient, projectId, clusterName); - - if (!cluster.connectionString) { - throw new Error("Connection string not available"); + if (cluster.connectionStrings === undefined) { + throw new Error("Connection strings not available"); + } + const connectionString = getConnectionString(cluster.connectionStrings, connectionType); + if (connectionString === undefined) { + throw new Error("Connection string not available for connection type: " + connectionType); } const username = `mcpUser${Math.floor(Math.random() * 100000)}`; @@ -200,7 +206,11 @@ export class ConnectClusterTool extends AtlasToolBase { }); } - protected async execute({ projectId, clusterName }: ToolArgs): Promise { + protected async execute({ + projectId, + clusterName, + connectionType, + }: ToolArgs): Promise { const ipAccessListUpdated = await ensureCurrentIpInAccessList(this.session.apiClient, projectId); let createdUser = false; @@ -239,7 +249,11 @@ export class ConnectClusterTool extends AtlasToolBase { case "disconnected": default: { await this.session.disconnect(); - const { connectionString, atlas } = await this.prepareClusterConnection(projectId, clusterName); + const { connectionString, atlas } = await this.prepareClusterConnection( + projectId, + clusterName, + connectionType + ); createdUser = true; // try to connect for about 5 minutes asynchronously From bbb363ca67778174643c35bdd8348f009f49083b Mon Sep 17 00:00:00 2001 From: Bianca Lisle Date: Thu, 9 Oct 2025 13:57:38 +0100 Subject: [PATCH 2/3] wip --- src/common/atlas/cluster.ts | 5 +++-- src/tools/atlas/connect/connectCluster.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/common/atlas/cluster.ts b/src/common/atlas/cluster.ts index 4ee8356f..6e7b27b8 100644 --- a/src/common/atlas/cluster.ts +++ b/src/common/atlas/cluster.ts @@ -71,7 +71,6 @@ export function formatCluster(cluster: ClusterDescription20240805): Cluster { const instanceSize = regionConfigs[0]?.instanceSize ?? "UNKNOWN"; const clusterInstanceType = instanceSize === "M0" ? "FREE" : "DEDICATED"; - const connectionString = cluster.connectionStrings?.standardSrv || cluster.connectionStrings?.standard; return { name: cluster.name, @@ -79,7 +78,6 @@ export function formatCluster(cluster: ClusterDescription20240805): Cluster { instanceSize: clusterInstanceType === "DEDICATED" ? instanceSize : undefined, state: cluster.stateName, mongoDBVersion: cluster.mongoDBVersion, - connectionString, connectionStrings: cluster.connectionStrings, processIds: extractProcessIds(cluster.connectionStrings?.standard ?? ""), }; @@ -89,6 +87,9 @@ export function getConnectionString( connectionStrings: ClusterConnectionStrings, connectionType: "standard" | "private" ): string | undefined { + if (connectionStrings === undefined) { + return undefined; + } if (connectionType === "standard") { return connectionStrings.standardSrv || connectionStrings.standard; } diff --git a/src/tools/atlas/connect/connectCluster.ts b/src/tools/atlas/connect/connectCluster.ts index 6db29237..ad1279ce 100644 --- a/src/tools/atlas/connect/connectCluster.ts +++ b/src/tools/atlas/connect/connectCluster.ts @@ -119,7 +119,7 @@ export class ConnectClusterTool extends AtlasToolBase { expiryDate, }; - const cn = new URL(cluster.connectionString); + const cn = new URL(connectionString); cn.username = username; cn.password = password; cn.searchParams.set("authSource", "admin"); From e06b16791ad21312b55defcfcdde5810e95314cd Mon Sep 17 00:00:00 2001 From: Bianca Lisle Date: Thu, 9 Oct 2025 17:45:37 +0100 Subject: [PATCH 3/3] update --- src/common/atlas/cluster.ts | 7 ++++--- src/tools/atlas/read/inspectCluster.ts | 2 +- src/tools/atlas/read/listClusters.ts | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/common/atlas/cluster.ts b/src/common/atlas/cluster.ts index 6e7b27b8..fed31672 100644 --- a/src/common/atlas/cluster.ts +++ b/src/common/atlas/cluster.ts @@ -22,7 +22,7 @@ export interface Cluster { instanceSize?: string; state?: "IDLE" | "CREATING" | "UPDATING" | "DELETING" | "REPAIRING"; mongoDBVersion?: string; - connectionString?: string; + standardConnectionString?: string; connectionStrings?: ClusterConnectionStrings; processIds?: Array; } @@ -35,7 +35,7 @@ export function formatFlexCluster(cluster: FlexClusterDescription20241113): Clus instanceSize: undefined, state: cluster.stateName, mongoDBVersion: cluster.mongoDBVersion, - connectionString, + standardConnectionString: connectionString, connectionStrings: cluster.connectionStrings, processIds: extractProcessIds(cluster.connectionStrings?.standard ?? ""), }; @@ -71,13 +71,14 @@ export function formatCluster(cluster: ClusterDescription20240805): Cluster { const instanceSize = regionConfigs[0]?.instanceSize ?? "UNKNOWN"; const clusterInstanceType = instanceSize === "M0" ? "FREE" : "DEDICATED"; - + const standardConnectionString = cluster.connectionStrings?.standardSrv || cluster.connectionStrings?.standard; return { name: cluster.name, instanceType: clusterInstanceType, instanceSize: clusterInstanceType === "DEDICATED" ? instanceSize : undefined, state: cluster.stateName, mongoDBVersion: cluster.mongoDBVersion, + standardConnectionString: standardConnectionString, connectionStrings: cluster.connectionStrings, processIds: extractProcessIds(cluster.connectionStrings?.standard ?? ""), }; diff --git a/src/tools/atlas/read/inspectCluster.ts b/src/tools/atlas/read/inspectCluster.ts index 56e1e5a8..09997713 100644 --- a/src/tools/atlas/read/inspectCluster.ts +++ b/src/tools/atlas/read/inspectCluster.ts @@ -30,7 +30,7 @@ export class InspectClusterTool extends AtlasToolBase { "Cluster details:", `Cluster Name | Cluster Type | Tier | State | MongoDB Version | Connection String ----------------|----------------|----------------|----------------|----------------|---------------- -${formattedCluster.name || "Unknown"} | ${formattedCluster.instanceType} | ${formattedCluster.instanceSize || "N/A"} | ${formattedCluster.state || "UNKNOWN"} | ${formattedCluster.mongoDBVersion || "N/A"} | ${formattedCluster.connectionString || "N/A"}` +${formattedCluster.name || "Unknown"} | ${formattedCluster.instanceType} | ${formattedCluster.instanceSize || "N/A"} | ${formattedCluster.state || "UNKNOWN"} | ${formattedCluster.mongoDBVersion || "N/A"} | ${formattedCluster.standardConnectionString || "N/A"}` ), }; } diff --git a/src/tools/atlas/read/listClusters.ts b/src/tools/atlas/read/listClusters.ts index 60344f7d..353009ab 100644 --- a/src/tools/atlas/read/listClusters.ts +++ b/src/tools/atlas/read/listClusters.ts @@ -105,7 +105,7 @@ ${rows}`, ----------------|----------------|----------------|----------------|----------------|---------------- ${allClusters .map((formattedCluster) => { - return `${formattedCluster.name || "Unknown"} | ${formattedCluster.instanceType} | ${formattedCluster.instanceSize || "N/A"} | ${formattedCluster.state || "UNKNOWN"} | ${formattedCluster.mongoDBVersion || "N/A"} | ${formattedCluster.connectionString || "N/A"}`; + return `${formattedCluster.name || "Unknown"} | ${formattedCluster.instanceType} | ${formattedCluster.instanceSize || "N/A"} | ${formattedCluster.state || "UNKNOWN"} | ${formattedCluster.mongoDBVersion || "N/A"} | ${formattedCluster.standardConnectionString || "N/A"}`; }) .join("\n")}` ),