From c7cf37bcbb24394123ec3cca4550bc453ad73730 Mon Sep 17 00:00:00 2001 From: Melanija Cvetic Date: Mon, 6 Oct 2025 14:35:00 +0100 Subject: [PATCH 01/10] feat: Implements atlasLocal tool atlas-local-connect-deployment --- package-lock.json | 48 +++++++++---------- package.json | 2 +- .../atlasLocal/connect/connectDeployment.ts | 34 +++++++++++++ src/tools/atlasLocal/tools.ts | 3 +- 4 files changed, 61 insertions(+), 26 deletions(-) create mode 100644 src/tools/atlasLocal/connect/connectDeployment.ts diff --git a/package-lock.json b/package-lock.json index d5fdf990..747320d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -75,7 +75,7 @@ "node": "^20.19.0 || ^22.12.0 || >= 23.0.0" }, "optionalDependencies": { - "@mongodb-js-preview/atlas-local": "^0.0.0-preview.3", + "@mongodb-js-preview/atlas-local": "^0.0.0-preview.6", "kerberos": "^2.2.2" } }, @@ -2050,26 +2050,26 @@ } }, "node_modules/@mongodb-js-preview/atlas-local": { - "version": "0.0.0-preview.3", - "resolved": "https://registry.npmjs.org/@mongodb-js-preview/atlas-local/-/atlas-local-0.0.0-preview.3.tgz", - "integrity": "sha512-Rq1xITOqTlGxr2mIQ4Ig0ugOs5cNzILN5g/zTm5RoXE6NHPY+qi86aNpQnJp/bQa4XR5BRvm4ztzFtBk1OGTvg==", + "version": "0.0.0-preview.6", + "resolved": "https://registry.npmjs.org/@mongodb-js-preview/atlas-local/-/atlas-local-0.0.0-preview.6.tgz", + "integrity": "sha512-UoLhTzyrL+99hGwcQyjjtOuWC66zQHgWhRXEj+tQVfDP97v0aKWctRigGVSmMPWnc2r5JdAlfR2M+sULfl7eCg==", "license": "Apache-2.0", "optional": true, "engines": { "node": ">= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0" }, "optionalDependencies": { - "@mongodb-js-preview/atlas-local-darwin-arm64": "0.0.0-preview.3", - "@mongodb-js-preview/atlas-local-darwin-x64": "0.0.0-preview.3", - "@mongodb-js-preview/atlas-local-linux-arm64-gnu": "0.0.0-preview.3", - "@mongodb-js-preview/atlas-local-linux-x64-gnu": "0.0.0-preview.3", - "@mongodb-js-preview/atlas-local-win32-x64-msvc": "0.0.0-preview.3" + "@mongodb-js-preview/atlas-local-darwin-arm64": "0.0.0-preview.6", + "@mongodb-js-preview/atlas-local-darwin-x64": "0.0.0-preview.6", + "@mongodb-js-preview/atlas-local-linux-arm64-gnu": "0.0.0-preview.6", + "@mongodb-js-preview/atlas-local-linux-x64-gnu": "0.0.0-preview.6", + "@mongodb-js-preview/atlas-local-win32-x64-msvc": "0.0.0-preview.6" } }, "node_modules/@mongodb-js-preview/atlas-local-darwin-arm64": { - "version": "0.0.0-preview.3", - "resolved": "https://registry.npmjs.org/@mongodb-js-preview/atlas-local-darwin-arm64/-/atlas-local-darwin-arm64-0.0.0-preview.3.tgz", - "integrity": "sha512-qEuXvFr1JtEdaPb85jP+69yCJIiXZHsQegOmlexpcrJwO6HXsn0JXryvO0wgay3BTiHmtUkmPvFcl2K4b6Q2rw==", + "version": "0.0.0-preview.6", + "resolved": "https://registry.npmjs.org/@mongodb-js-preview/atlas-local-darwin-arm64/-/atlas-local-darwin-arm64-0.0.0-preview.6.tgz", + "integrity": "sha512-xB2h87mdq0W1tq7wZmpUIzS/wmUOrEKRqUYKNbv1g6M8pY/1ln2OCPi1OGj1LUnT8dC/jvklh92Ittj6eb5leg==", "cpu": [ "arm64" ], @@ -2083,9 +2083,9 @@ } }, "node_modules/@mongodb-js-preview/atlas-local-darwin-x64": { - "version": "0.0.0-preview.3", - "resolved": "https://registry.npmjs.org/@mongodb-js-preview/atlas-local-darwin-x64/-/atlas-local-darwin-x64-0.0.0-preview.3.tgz", - "integrity": "sha512-QghS4XmDpaPZdtMev1XKMfFdJ3Tvhfaaa8ZTV3mIQOFuy200eBwTM/xQaZtBLw9TQUqK7pvxH+nvv+iBeNMK1A==", + "version": "0.0.0-preview.6", + "resolved": "https://registry.npmjs.org/@mongodb-js-preview/atlas-local-darwin-x64/-/atlas-local-darwin-x64-0.0.0-preview.6.tgz", + "integrity": "sha512-5o4fXyXm6lOB1vCOIxRiF2p4NpChFZtjLSvsal+nxHBHJb/tevl7k8kE+dt7a5guO7CW1TCfy71E58NQxxaxkQ==", "cpu": [ "x64" ], @@ -2099,9 +2099,9 @@ } }, "node_modules/@mongodb-js-preview/atlas-local-linux-arm64-gnu": { - "version": "0.0.0-preview.3", - "resolved": "https://registry.npmjs.org/@mongodb-js-preview/atlas-local-linux-arm64-gnu/-/atlas-local-linux-arm64-gnu-0.0.0-preview.3.tgz", - "integrity": "sha512-b7IqwkrZ7VL8zDJhu79hY6hj7RqVcFxCF/QV5xR2tsfzIvoqChBilw7AcsuqGS+vws2aBhMp7qKl+YkaSuRblg==", + "version": "0.0.0-preview.6", + "resolved": "https://registry.npmjs.org/@mongodb-js-preview/atlas-local-linux-arm64-gnu/-/atlas-local-linux-arm64-gnu-0.0.0-preview.6.tgz", + "integrity": "sha512-ul8odgYVF5jpW56u2WHMs82Pmaf/6giIjQxHbMMafcHUPC87R08+Y0qIp8iY0vl4lTE5CBPXNzIICyPp+Snw9Q==", "cpu": [ "arm64" ], @@ -2115,9 +2115,9 @@ } }, "node_modules/@mongodb-js-preview/atlas-local-linux-x64-gnu": { - "version": "0.0.0-preview.3", - "resolved": "https://registry.npmjs.org/@mongodb-js-preview/atlas-local-linux-x64-gnu/-/atlas-local-linux-x64-gnu-0.0.0-preview.3.tgz", - "integrity": "sha512-oR8D5u5+CSYfS206Mw4MkFy5HQS6H7+uGnIgBCE/qK7OQ/WVi9TZIfD+hXrtoSLPOlitmcyODdWGcBfBmb3C/Q==", + "version": "0.0.0-preview.6", + "resolved": "https://registry.npmjs.org/@mongodb-js-preview/atlas-local-linux-x64-gnu/-/atlas-local-linux-x64-gnu-0.0.0-preview.6.tgz", + "integrity": "sha512-90t5SynjHFvhEgh6xXEr86tN5VnKHDK6LrOb9Yuf/IlMHNmFm85Cr/OD/B5jREGxlLl+rWWZE7f+EZn/5pWVgw==", "cpu": [ "x64" ], @@ -2131,9 +2131,9 @@ } }, "node_modules/@mongodb-js-preview/atlas-local-win32-x64-msvc": { - "version": "0.0.0-preview.3", - "resolved": "https://registry.npmjs.org/@mongodb-js-preview/atlas-local-win32-x64-msvc/-/atlas-local-win32-x64-msvc-0.0.0-preview.3.tgz", - "integrity": "sha512-epjn0O61f9hKhyTyR8fhYkhEEAJI8kZARBuO4bdvbVJOQf6i/v1fY0OCaPLARznHj1ap1IXlQFax+gSF/4wMPQ==", + "version": "0.0.0-preview.6", + "resolved": "https://registry.npmjs.org/@mongodb-js-preview/atlas-local-win32-x64-msvc/-/atlas-local-win32-x64-msvc-0.0.0-preview.6.tgz", + "integrity": "sha512-sxwT5NMMffrHNuPbaywuuu91OZC0CTSJaTQpjdjYWQgdeQDMDyWFNWS6k6irgxbgEMwacv6HUKPuOE/fC+/uNA==", "cpu": [ "x64" ], diff --git a/package.json b/package.json index 58004422..4a685ad1 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,7 @@ "node": "^20.19.0 || ^22.12.0 || >= 23.0.0" }, "optionalDependencies": { - "@mongodb-js-preview/atlas-local": "^0.0.0-preview.3", + "@mongodb-js-preview/atlas-local": "^0.0.0-preview.6", "kerberos": "^2.2.2" } } diff --git a/src/tools/atlasLocal/connect/connectDeployment.ts b/src/tools/atlasLocal/connect/connectDeployment.ts new file mode 100644 index 00000000..e34e2cf3 --- /dev/null +++ b/src/tools/atlasLocal/connect/connectDeployment.ts @@ -0,0 +1,34 @@ +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { AtlasLocalToolBase } from "../atlasLocalTool.js"; +import type { OperationType, ToolArgs } from "../../tool.js"; +import type { Client } from "@mongodb-js-preview/atlas-local"; +import { z } from "zod"; + +export class ConnectDeploymentTool extends AtlasLocalToolBase { + public name = "atlas-local-connect-deployment"; + protected description = "Connect to a MongoDB Atlas Local deployment"; + public operationType: OperationType = "connect"; + protected argsShape = { + deploymentIdOrName: z.string().describe("Name or ID of the deployment to connect to"), + }; + + protected async executeWithAtlasLocalClient( + client: Client, + { deploymentIdOrName }: ToolArgs + ): Promise { + // Get the connection string for the deployment + const connectionString = await client.getConnectionString(deploymentIdOrName); + + // Connect to the deployment + await this.session.connectToMongoDB({ connectionString }); + + return { + content: [ + { + type: "text", + text: `Successfully connected to Atlas Local deployment "${deploymentIdOrName}".`, + }, + ], + }; + } +} diff --git a/src/tools/atlasLocal/tools.ts b/src/tools/atlasLocal/tools.ts index 655ae1dc..451362ce 100644 --- a/src/tools/atlasLocal/tools.ts +++ b/src/tools/atlasLocal/tools.ts @@ -1,5 +1,6 @@ import { DeleteDeploymentTool } from "./delete/deleteDeployment.js"; import { ListDeploymentsTool } from "./read/listDeployments.js"; import { CreateDeploymentTool } from "./create/createDeployment.js"; +import { ConnectDeploymentTool } from "./connect/connectDeployment.js"; -export const AtlasLocalTools = [ListDeploymentsTool, DeleteDeploymentTool, CreateDeploymentTool]; +export const AtlasLocalTools = [ListDeploymentsTool, DeleteDeploymentTool, CreateDeploymentTool, ConnectDeploymentTool]; From 0d0581dbab9160eda9ff9d0d0d208b5a0baa1d32 Mon Sep 17 00:00:00 2001 From: Melanija Cvetic Date: Mon, 6 Oct 2025 15:06:42 +0100 Subject: [PATCH 02/10] Adds integration tests --- .../atlas-local/connectDeployment.test.ts | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 tests/integration/tools/atlas-local/connectDeployment.test.ts diff --git a/tests/integration/tools/atlas-local/connectDeployment.test.ts b/tests/integration/tools/atlas-local/connectDeployment.test.ts new file mode 100644 index 00000000..b266ec9b --- /dev/null +++ b/tests/integration/tools/atlas-local/connectDeployment.test.ts @@ -0,0 +1,98 @@ +import { + defaultDriverOptions, + defaultTestConfig, + expectDefined, + getResponseElements, + setupIntegrationTest, + waitUntilMcpClientIsSet, +} from "../../helpers.js"; +import { describe, expect, it } from "vitest"; + +const isMacOSInGitHubActions = process.platform === "darwin" && process.env.GITHUB_ACTIONS === "true"; + +// Docker is not available on macOS in GitHub Actions +// That's why we skip the tests on macOS in GitHub Actions +describe("atlas-local-connect-deployment", () => { + const integration = setupIntegrationTest( + () => defaultTestConfig, + () => defaultDriverOptions + ); + + it.skipIf(isMacOSInGitHubActions)("should have the atlas-local-connect-deployment tool", async ({ signal }) => { + await waitUntilMcpClientIsSet(integration.mcpServer(), signal); + + const { tools } = await integration.mcpClient().listTools(); + const connectDeployment = tools.find((tool) => tool.name === "atlas-local-connect-deployment"); + expectDefined(connectDeployment); + }); + + it.skipIf(!isMacOSInGitHubActions)( + "[MacOS in GitHub Actions] should not have the atlas-local-connect-deployment tool", + async ({ signal }) => { + // This should throw an error because the client is not set within the timeout of 5 seconds (default) + await expect(waitUntilMcpClientIsSet(integration.mcpServer(), signal)).rejects.toThrow(); + + const { tools } = await integration.mcpClient().listTools(); + const connectDeployment = tools.find((tool) => tool.name === "atlas-local-connect-deployment"); + expect(connectDeployment).toBeUndefined(); + } + ); + + it.skipIf(isMacOSInGitHubActions)("should have correct metadata", async ({ signal }) => { + await waitUntilMcpClientIsSet(integration.mcpServer(), signal); + const { tools } = await integration.mcpClient().listTools(); + const connectDeployment = tools.find((tool) => tool.name === "atlas-local-connect-deployment"); + expectDefined(connectDeployment); + expect(connectDeployment.inputSchema.type).toBe("object"); + expectDefined(connectDeployment.inputSchema.properties); + expect(connectDeployment.inputSchema.properties).toHaveProperty("deploymentIdOrName"); + }); + + it.skipIf(isMacOSInGitHubActions)( + "should return 'no such container' error when connecting to non-existent deployment", + async ({ signal }) => { + await waitUntilMcpClientIsSet(integration.mcpServer(), signal); + + const response = await integration.mcpClient().callTool({ + name: "atlas-local-connect-deployment", + arguments: { deploymentIdOrName: "non-existent" }, + }); + const elements = getResponseElements(response.content); + expect(elements.length).toBeGreaterThanOrEqual(1); + expect(elements[0]?.text).toContain( + "Docker responded with status code 404: No such container: non-existent" + ); + } + ); + + it.skipIf(isMacOSInGitHubActions)("should connect to a deployment when calling the tool", async ({ signal }) => { + await waitUntilMcpClientIsSet(integration.mcpServer(), signal); + // Create a deployment + const deploymentName = `test-deployment-${Date.now()}`; + await integration.mcpClient().callTool({ + name: "atlas-local-create-deployment", + arguments: { deploymentName }, + }); + + // Connect to the deployment + const response = await integration.mcpClient().callTool({ + name: "atlas-local-connect-deployment", + arguments: { deploymentIdOrName: deploymentName }, + }); + const elements = getResponseElements(response.content); + expect(elements.length).toBeGreaterThanOrEqual(1); + expect(elements[0]?.text).toContain( + 'Successfully connected to Atlas Local deployment "' + deploymentName + '".' + ); + + // cleanup + try { + await integration.mcpClient().callTool({ + name: "atlas-local-delete-deployment", + arguments: { deploymentName }, + }); + } catch (error) { + console.warn(`Failed to delete deployment ${deploymentName}:`, error); + } + }); +}); From 0c53f48963692f9e518cc0747cbddd53e9a82417 Mon Sep 17 00:00:00 2001 From: Melanija Cvetic Date: Mon, 6 Oct 2025 15:31:06 +0100 Subject: [PATCH 03/10] adds accuracy tests --- tests/accuracy/connectDeployment.test.ts | 70 ++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 tests/accuracy/connectDeployment.test.ts diff --git a/tests/accuracy/connectDeployment.test.ts b/tests/accuracy/connectDeployment.test.ts new file mode 100644 index 00000000..57ca7a05 --- /dev/null +++ b/tests/accuracy/connectDeployment.test.ts @@ -0,0 +1,70 @@ +import { describeAccuracyTests } from "./sdk/describeAccuracyTests.js"; +import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; + +describeAccuracyTests([ + { + prompt: "Connect to the local MongoDB cluster called 'my-database'", + expectedToolCalls: [ + { + toolName: "atlas-local-connect-deployment", + parameters: { + deploymentIdOrName: "my-database", + }, + }, + ], + }, + { + prompt: "Connect to the local MongoDB atlas database called 'my-instance'", + expectedToolCalls: [ + { + toolName: "atlas-local-connect-deployment", + parameters: { + deploymentIdOrName: "my-instance", + }, + }, + ], + }, + { + prompt: "If and only if, the local MongoDB deployment 'local-mflix' exists, then connect to it", + mockedTools: { + "atlas-local-list-deployments": (): CallToolResult => ({ + content: [ + { type: "text", text: "Found 1 deployment:" }, + { + type: "text", + text: "Deployment Name | State | MongoDB Version\n----------------|----------------|----------------\nlocal-mflix | Running | 6.0", + }, + ], + }), + }, + expectedToolCalls: [ + { + toolName: "atlas-local-list-deployments", + parameters: {}, + }, + { + toolName: "atlas-local-connect-deployment", + parameters: { + deploymentIdOrName: "local-mflix", + }, + }, + ], + }, + { + prompt: "Connect to a new local MongoDB cluster named 'local-mflix'", + expectedToolCalls: [ + { + toolName: "atlas-local-create-deployment", + parameters: { + deploymentName: "local-mflix", + }, + }, + { + toolName: "atlas-local-connect-deployment", + parameters: { + deploymentIdOrName: "local-mflix", + }, + }, + ], + }, +]); From ba884f84d323e3c6984f4da5fe4ca3749c8b6351 Mon Sep 17 00:00:00 2001 From: Melanija Cvetic Date: Mon, 6 Oct 2025 15:59:22 +0100 Subject: [PATCH 04/10] Adds hints to llm on which connect tool to use and fixes atlas test --- src/common/connectionErrorHandler.ts | 15 ++++++++++++--- tests/integration/tools/atlas/clusters.test.ts | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/common/connectionErrorHandler.ts b/src/common/connectionErrorHandler.ts index 9de63bef..68a47d57 100644 --- a/src/common/connectionErrorHandler.ts +++ b/src/common/connectionErrorHandler.ts @@ -20,9 +20,18 @@ export const connectionErrorHandler: ConnectionErrorHandler = (error, { availabl // Find the first Atlas connect tool if available and suggest to the LLM to use it. // Note: if we ever have multiple Atlas connect tools, we may want to refine this logic to select the most appropriate one. const atlasConnectTool = connectTools?.find((t) => t.category === "atlas"); - const llmConnectHint = atlasConnectTool - ? `Note to LLM: prefer using the "${atlasConnectTool.name}" tool to connect to an Atlas cluster over using a connection string. Make sure to ask the user to specify a cluster name they want to connect to or ask them if they want to use the "list-clusters" tool to list all their clusters. Do not invent cluster names or connection strings unless the user has explicitly specified them. If they've previously connected to MongoDB using MCP, you can ask them if they want to reconnect using the same cluster/connection.` - : "Note to LLM: do not invent connection strings and explicitly ask the user to provide one. If they have previously connected to MongoDB using MCP, you can ask them if they want to reconnect using the same connection string."; + const atlasLocalConnectTool = connectTools?.find((t) => t.category === "atlas-local"); + + let llmConnectHint: string; + + if (atlasConnectTool) { + llmConnectHint = `Note to LLM: prefer using the "${atlasConnectTool.name}" tool to connect to an Atlas cluster over using a connection string. Make sure to ask the user to specify a cluster name they want to connect to or ask them if they want to use the "list-clusters" tool to list all their clusters. Do not invent cluster names or connection strings unless the user has explicitly specified them. If they've previously connected to MongoDB using MCP, you can ask them if they want to reconnect using the same cluster/connection.`; + } else if (atlasLocalConnectTool) { + llmConnectHint = `Note to LLM: prefer using the "${atlasLocalConnectTool.name}" tool for connecting to local MongoDB deployments. Ask the user to specify a deployment name or use "atlas-local-list-deployments" to show available local deployments. Do not invent deployment names unless the user has explicitly specified them. If they've previously connected to a local MongoDB deployment using MCP, you can ask them if they want to reconnect using the same deployment.`; + } else { + llmConnectHint = + "Note to LLM: do not invent connection strings and explicitly ask the user to provide one. If they have previously connected to MongoDB using MCP, you can ask them if they want to reconnect using the same connection string."; + } const connectToolsNames = connectTools?.map((t) => `"${t.name}"`).join(", "); const additionalPromptForConnectivity: { type: "text"; text: string }[] = []; diff --git a/tests/integration/tools/atlas/clusters.test.ts b/tests/integration/tools/atlas/clusters.test.ts index ccb6e774..66720241 100644 --- a/tests/integration/tools/atlas/clusters.test.ts +++ b/tests/integration/tools/atlas/clusters.test.ts @@ -239,7 +239,7 @@ describeWithAtlas("clusters", (integration) => { "You need to connect to a MongoDB instance before you can access its data." ); expect(elements[1]?.text).toContain( - 'Please use one of the following tools: "atlas-connect-cluster", "connect" to connect to a MongoDB instance' + 'Please use one of the following tools: "atlas-connect-cluster", "atlas-local-connect-deployment", "connect" to connect to a MongoDB instance' ); }); }); From 975909e30c4141353ea44e93fbd73330f9c27576 Mon Sep 17 00:00:00 2001 From: Melanija Cvetic Date: Mon, 6 Oct 2025 17:46:39 +0100 Subject: [PATCH 05/10] Moves cleanup to afterEach block --- .../atlas-local/connectDeployment.test.ts | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/tests/integration/tools/atlas-local/connectDeployment.test.ts b/tests/integration/tools/atlas-local/connectDeployment.test.ts index b266ec9b..6ba135b3 100644 --- a/tests/integration/tools/atlas-local/connectDeployment.test.ts +++ b/tests/integration/tools/atlas-local/connectDeployment.test.ts @@ -6,13 +6,29 @@ import { setupIntegrationTest, waitUntilMcpClientIsSet, } from "../../helpers.js"; -import { describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it } from "vitest"; const isMacOSInGitHubActions = process.platform === "darwin" && process.env.GITHUB_ACTIONS === "true"; // Docker is not available on macOS in GitHub Actions // That's why we skip the tests on macOS in GitHub Actions describe("atlas-local-connect-deployment", () => { + let deploymentNamesToCleanup: string[] = []; + + afterEach(async () => { + // Clean up any deployments created during the test + for (const deploymentName of deploymentNamesToCleanup) { + try { + await integration.mcpClient().callTool({ + name: "atlas-local-delete-deployment", + arguments: { deploymentName }, + }); + } catch (error) { + console.warn(`Failed to delete deployment ${deploymentName}:`, error); + } + } + deploymentNamesToCleanup = []; + }); const integration = setupIntegrationTest( () => defaultTestConfig, () => defaultDriverOptions @@ -69,6 +85,7 @@ describe("atlas-local-connect-deployment", () => { await waitUntilMcpClientIsSet(integration.mcpServer(), signal); // Create a deployment const deploymentName = `test-deployment-${Date.now()}`; + deploymentNamesToCleanup.push(deploymentName); await integration.mcpClient().callTool({ name: "atlas-local-create-deployment", arguments: { deploymentName }, @@ -84,15 +101,5 @@ describe("atlas-local-connect-deployment", () => { expect(elements[0]?.text).toContain( 'Successfully connected to Atlas Local deployment "' + deploymentName + '".' ); - - // cleanup - try { - await integration.mcpClient().callTool({ - name: "atlas-local-delete-deployment", - arguments: { deploymentName }, - }); - } catch (error) { - console.warn(`Failed to delete deployment ${deploymentName}:`, error); - } }); }); From 9ad0257cc080c07d7eea264ebc9457d39571ad8e Mon Sep 17 00:00:00 2001 From: Melanija Cvetic Date: Tue, 7 Oct 2025 13:28:25 +0100 Subject: [PATCH 06/10] adds error handling for when container non-existent --- src/tools/atlasLocal/atlasLocalTool.ts | 14 ++++++++++++++ .../tools/atlas-local/deleteDeployment.test.ts | 5 +++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/tools/atlasLocal/atlasLocalTool.ts b/src/tools/atlasLocal/atlasLocalTool.ts index b67e83bd..4c97f3e7 100644 --- a/src/tools/atlasLocal/atlasLocalTool.ts +++ b/src/tools/atlasLocal/atlasLocalTool.ts @@ -48,6 +48,20 @@ please log a ticket here: https://github.com/mongodb-js/mongodb-mcp-server/issue args: ToolArgs ): Promise | CallToolResult { // Error Handling for expected Atlas Local errors go here + const errorMessage = error instanceof Error ? error.message : String(error); + if (errorMessage.includes("No such container")) { + const deploymentName = + "deploymentName" in args ? (args.deploymentName as string) : "the specified deployment"; + return { + content: [ + { + type: "text", + text: `The Atlas Local deployment "${deploymentName}" was not found. Please check the deployment name or use "atlas-local-list-deployments" to see available deployments.`, + }, + ], + isError: true, + }; + } // For other types of errors, use the default error handling from the base class return super.handleError(error, args); diff --git a/tests/integration/tools/atlas-local/deleteDeployment.test.ts b/tests/integration/tools/atlas-local/deleteDeployment.test.ts index 6956da91..f1125d53 100644 --- a/tests/integration/tools/atlas-local/deleteDeployment.test.ts +++ b/tests/integration/tools/atlas-local/deleteDeployment.test.ts @@ -52,15 +52,16 @@ describe("atlas-local-delete-deployment", () => { "should return 'no such container' error when deployment to delete does not exist", async ({ signal }) => { await waitUntilMcpClientIsSet(integration.mcpServer(), signal); + const deploymentName = "non-existent"; const response = await integration.mcpClient().callTool({ name: "atlas-local-delete-deployment", - arguments: { deploymentName: "non-existent" }, + arguments: { deploymentName }, }); const elements = getResponseElements(response.content); expect(elements.length).toBeGreaterThanOrEqual(1); expect(elements[0]?.text).toContain( - "Docker responded with status code 404: No such container: non-existent" + `The Atlas Local deployment "${deploymentName}" was not found. Please check the deployment name or use "atlas-local-list-deployments" to see available deployments.` ); } ); From fa6d61674ceb548700dc120df9e0372888cfa79e Mon Sep 17 00:00:00 2001 From: Melanija Cvetic Date: Tue, 7 Oct 2025 13:31:05 +0100 Subject: [PATCH 07/10] tool input doesnt take deployment id --- src/tools/atlasLocal/connect/connectDeployment.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tools/atlasLocal/connect/connectDeployment.ts b/src/tools/atlasLocal/connect/connectDeployment.ts index e34e2cf3..e2420aef 100644 --- a/src/tools/atlasLocal/connect/connectDeployment.ts +++ b/src/tools/atlasLocal/connect/connectDeployment.ts @@ -9,15 +9,15 @@ export class ConnectDeploymentTool extends AtlasLocalToolBase { protected description = "Connect to a MongoDB Atlas Local deployment"; public operationType: OperationType = "connect"; protected argsShape = { - deploymentIdOrName: z.string().describe("Name or ID of the deployment to connect to"), + deploymentName: z.string().describe("Name of the deployment to connect to"), }; protected async executeWithAtlasLocalClient( client: Client, - { deploymentIdOrName }: ToolArgs + { deploymentName }: ToolArgs ): Promise { // Get the connection string for the deployment - const connectionString = await client.getConnectionString(deploymentIdOrName); + const connectionString = await client.getConnectionString(deploymentName); // Connect to the deployment await this.session.connectToMongoDB({ connectionString }); @@ -26,7 +26,7 @@ export class ConnectDeploymentTool extends AtlasLocalToolBase { content: [ { type: "text", - text: `Successfully connected to Atlas Local deployment "${deploymentIdOrName}".`, + text: `Successfully connected to Atlas Local deployment "${deploymentName}".`, }, ], }; From a169d63076556509f72255eeabf69b0a206b797b Mon Sep 17 00:00:00 2001 From: Melanija Cvetic Date: Tue, 7 Oct 2025 13:31:34 +0100 Subject: [PATCH 08/10] cluster integ test to handle macos in github runner --- tests/integration/tools/atlas/clusters.test.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/integration/tools/atlas/clusters.test.ts b/tests/integration/tools/atlas/clusters.test.ts index 66720241..fc168318 100644 --- a/tests/integration/tools/atlas/clusters.test.ts +++ b/tests/integration/tools/atlas/clusters.test.ts @@ -238,9 +238,17 @@ describeWithAtlas("clusters", (integration) => { expect(elements[0]?.text).toContain( "You need to connect to a MongoDB instance before you can access its data." ); - expect(elements[1]?.text).toContain( - 'Please use one of the following tools: "atlas-connect-cluster", "atlas-local-connect-deployment", "connect" to connect to a MongoDB instance' - ); + // Check if the response contains all available test tools. + if (process.platform === "darwin" && process.env.GITHUB_ACTIONS === "true") { + // The tool atlas-local-connect-deployment may be disabled in some test environments if Docker is not available. + expect(elements[1]?.text).toContain( + 'Please use one of the following tools: "atlas-connect-cluster", "connect" to connect to a MongoDB instance' + ); + } else { + expect(elements[1]?.text).toContain( + 'Please use one of the following tools: "atlas-connect-cluster", "atlas-local-connect-deployment", "connect" to connect to a MongoDB instance' + ); + } }); }); }); From 6bf04ce9c0ff5bf5b465f2456d2b0780eb64f6b2 Mon Sep 17 00:00:00 2001 From: Melanija Cvetic Date: Tue, 7 Oct 2025 13:31:49 +0100 Subject: [PATCH 09/10] restructures test --- .../atlas-local/connectDeployment.test.ts | 139 ++++++++++-------- 1 file changed, 74 insertions(+), 65 deletions(-) diff --git a/tests/integration/tools/atlas-local/connectDeployment.test.ts b/tests/integration/tools/atlas-local/connectDeployment.test.ts index 6ba135b3..16368211 100644 --- a/tests/integration/tools/atlas-local/connectDeployment.test.ts +++ b/tests/integration/tools/atlas-local/connectDeployment.test.ts @@ -1,105 +1,114 @@ +import { beforeEach } from "vitest"; import { defaultDriverOptions, defaultTestConfig, expectDefined, getResponseElements, setupIntegrationTest, + validateToolMetadata, waitUntilMcpClientIsSet, } from "../../helpers.js"; import { afterEach, describe, expect, it } from "vitest"; const isMacOSInGitHubActions = process.platform === "darwin" && process.env.GITHUB_ACTIONS === "true"; +const integration = setupIntegrationTest( + () => defaultTestConfig, + () => defaultDriverOptions +); // Docker is not available on macOS in GitHub Actions // That's why we skip the tests on macOS in GitHub Actions -describe("atlas-local-connect-deployment", () => { - let deploymentNamesToCleanup: string[] = []; - - afterEach(async () => { - // Clean up any deployments created during the test - for (const deploymentName of deploymentNamesToCleanup) { - try { - await integration.mcpClient().callTool({ - name: "atlas-local-delete-deployment", - arguments: { deploymentName }, - }); - } catch (error) { - console.warn(`Failed to delete deployment ${deploymentName}:`, error); - } - } - deploymentNamesToCleanup = []; - }); - const integration = setupIntegrationTest( - () => defaultTestConfig, - () => defaultDriverOptions - ); - - it.skipIf(isMacOSInGitHubActions)("should have the atlas-local-connect-deployment tool", async ({ signal }) => { +describe.skipIf(isMacOSInGitHubActions)("atlas-local-connect-deployment", () => { + beforeEach(async ({ signal }) => { await waitUntilMcpClientIsSet(integration.mcpServer(), signal); - - const { tools } = await integration.mcpClient().listTools(); - const connectDeployment = tools.find((tool) => tool.name === "atlas-local-connect-deployment"); - expectDefined(connectDeployment); }); - it.skipIf(!isMacOSInGitHubActions)( - "[MacOS in GitHub Actions] should not have the atlas-local-connect-deployment tool", - async ({ signal }) => { - // This should throw an error because the client is not set within the timeout of 5 seconds (default) - await expect(waitUntilMcpClientIsSet(integration.mcpServer(), signal)).rejects.toThrow(); - - const { tools } = await integration.mcpClient().listTools(); - const connectDeployment = tools.find((tool) => tool.name === "atlas-local-connect-deployment"); - expect(connectDeployment).toBeUndefined(); - } - ); + validateToolMetadata(integration, "atlas-local-connect-deployment", "Connect to a MongoDB Atlas Local deployment", [ + { + name: "deploymentName", + type: "string", + description: "Name of the deployment to connect to", + required: true, + }, + ]); - it.skipIf(isMacOSInGitHubActions)("should have correct metadata", async ({ signal }) => { - await waitUntilMcpClientIsSet(integration.mcpServer(), signal); + it("should have the atlas-local-connect-deployment tool", async () => { const { tools } = await integration.mcpClient().listTools(); const connectDeployment = tools.find((tool) => tool.name === "atlas-local-connect-deployment"); expectDefined(connectDeployment); - expect(connectDeployment.inputSchema.type).toBe("object"); - expectDefined(connectDeployment.inputSchema.properties); - expect(connectDeployment.inputSchema.properties).toHaveProperty("deploymentIdOrName"); }); - it.skipIf(isMacOSInGitHubActions)( - "should return 'no such container' error when connecting to non-existent deployment", - async ({ signal }) => { - await waitUntilMcpClientIsSet(integration.mcpServer(), signal); + it("should return 'no such container' error when connecting to non-existent deployment", async () => { + const deploymentName = "non-existent"; + const response = await integration.mcpClient().callTool({ + name: "atlas-local-connect-deployment", + arguments: { deploymentName }, + }); + const elements = getResponseElements(response.content); + expect(elements.length).toBeGreaterThanOrEqual(1); + expect(elements[0]?.text).toContain( + `The Atlas Local deployment "${deploymentName}" was not found. Please check the deployment name or use "atlas-local-list-deployments" to see available deployments.` + ); + }); +}); - const response = await integration.mcpClient().callTool({ - name: "atlas-local-connect-deployment", - arguments: { deploymentIdOrName: "non-existent" }, - }); - const elements = getResponseElements(response.content); - expect(elements.length).toBeGreaterThanOrEqual(1); - expect(elements[0]?.text).toContain( - "Docker responded with status code 404: No such container: non-existent" - ); - } - ); +describe.skipIf(isMacOSInGitHubActions)("atlas-local-connect-deployment with deployments", () => { + let deploymentName: string = ""; + let deploymentNamesToCleanup: string[] = []; - it.skipIf(isMacOSInGitHubActions)("should connect to a deployment when calling the tool", async ({ signal }) => { + beforeEach(async ({ signal }) => { await waitUntilMcpClientIsSet(integration.mcpServer(), signal); - // Create a deployment - const deploymentName = `test-deployment-${Date.now()}`; + + // Create deployments + deploymentName = `test-deployment-1-${Date.now()}`; deploymentNamesToCleanup.push(deploymentName); await integration.mcpClient().callTool({ name: "atlas-local-create-deployment", arguments: { deploymentName }, }); + const anotherDeploymentName = `test-deployment-2-${Date.now()}`; + deploymentNamesToCleanup.push(anotherDeploymentName); + await integration.mcpClient().callTool({ + name: "atlas-local-create-deployment", + arguments: { deploymentName: anotherDeploymentName }, + }); + }); + + afterEach(async () => { + // Delete all created deployments + for (const deploymentNameToCleanup of deploymentNamesToCleanup) { + try { + await integration.mcpClient().callTool({ + name: "atlas-local-delete-deployment", + arguments: { deploymentName: deploymentNameToCleanup }, + }); + } catch (error) { + console.warn(`Failed to delete deployment ${deploymentNameToCleanup}:`, error); + } + } + deploymentNamesToCleanup = []; + }); + + it("should connect to correct deployment when calling the tool", async () => { // Connect to the deployment const response = await integration.mcpClient().callTool({ name: "atlas-local-connect-deployment", - arguments: { deploymentIdOrName: deploymentName }, + arguments: { deploymentName }, }); const elements = getResponseElements(response.content); expect(elements.length).toBeGreaterThanOrEqual(1); - expect(elements[0]?.text).toContain( - 'Successfully connected to Atlas Local deployment "' + deploymentName + '".' - ); + expect(elements[0]?.text).toContain(`Successfully connected to Atlas Local deployment "${deploymentName}".`); + }); +}); + +describe.skipIf(!isMacOSInGitHubActions)("atlas-local-connect-deployment [MacOS in GitHub Actions]", () => { + it("should not have the atlas-local-connect-deployment tool", async ({ signal }) => { + // This should throw an error because the client is not set within the timeout of 5 seconds (default) + await expect(waitUntilMcpClientIsSet(integration.mcpServer(), signal)).rejects.toThrow(); + + const { tools } = await integration.mcpClient().listTools(); + const connectDeployment = tools.find((tool) => tool.name === "atlas-local-connect-deployment"); + expect(connectDeployment).toBeUndefined(); }); }); From 74fe9fe44d00c0351a09cf7b6bab08df0e15ad97 Mon Sep 17 00:00:00 2001 From: Melanija Cvetic Date: Tue, 7 Oct 2025 15:10:09 +0100 Subject: [PATCH 10/10] Improves llmConntectHint --- src/common/connectionErrorHandler.ts | 30 ++++++++++++++++++---------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/common/connectionErrorHandler.ts b/src/common/connectionErrorHandler.ts index 68a47d57..30b63796 100644 --- a/src/common/connectionErrorHandler.ts +++ b/src/common/connectionErrorHandler.ts @@ -17,21 +17,29 @@ export const connectionErrorHandler: ConnectionErrorHandler = (error, { availabl .filter((t) => t.operationType === "connect") .sort((a, b) => a.category.localeCompare(b.category)); // Sort Atlas tools before MongoDB tools - // Find the first Atlas connect tool if available and suggest to the LLM to use it. - // Note: if we ever have multiple Atlas connect tools, we may want to refine this logic to select the most appropriate one. + // Find what Atlas connect tools are available and suggest when the LLM should to use each. If no Atlas tools are found, return a suggestion for the MongoDB connect tool. const atlasConnectTool = connectTools?.find((t) => t.category === "atlas"); const atlasLocalConnectTool = connectTools?.find((t) => t.category === "atlas-local"); - let llmConnectHint: string; + const llmConnectHint = ((): string => { + const hints: string[] = []; - if (atlasConnectTool) { - llmConnectHint = `Note to LLM: prefer using the "${atlasConnectTool.name}" tool to connect to an Atlas cluster over using a connection string. Make sure to ask the user to specify a cluster name they want to connect to or ask them if they want to use the "list-clusters" tool to list all their clusters. Do not invent cluster names or connection strings unless the user has explicitly specified them. If they've previously connected to MongoDB using MCP, you can ask them if they want to reconnect using the same cluster/connection.`; - } else if (atlasLocalConnectTool) { - llmConnectHint = `Note to LLM: prefer using the "${atlasLocalConnectTool.name}" tool for connecting to local MongoDB deployments. Ask the user to specify a deployment name or use "atlas-local-list-deployments" to show available local deployments. Do not invent deployment names unless the user has explicitly specified them. If they've previously connected to a local MongoDB deployment using MCP, you can ask them if they want to reconnect using the same deployment.`; - } else { - llmConnectHint = - "Note to LLM: do not invent connection strings and explicitly ask the user to provide one. If they have previously connected to MongoDB using MCP, you can ask them if they want to reconnect using the same connection string."; - } + if (atlasConnectTool) { + hints.push( + `Note to LLM: prefer using the "${atlasConnectTool.name}" tool to connect to an Atlas cluster over using a connection string. Make sure to ask the user to specify a cluster name they want to connect to or ask them if they want to use the "list-clusters" tool to list all their clusters. Do not invent cluster names or connection strings unless the user has explicitly specified them. If they've previously connected to MongoDB using MCP, you can ask them if they want to reconnect using the same cluster/connection.` + ); + } + + if (atlasLocalConnectTool) { + hints.push( + `Note to LLM: For MongoDB Atlas Local deployments, ask the user to either provide a connection string, specify a deployment name, or use "atlas-local-list-deployments" to show available local deployments. If a deployment name is provided, prefer using the "${atlasLocalConnectTool.name}" tool. If a connection string is provided, prefer using the "connect" tool. Do not invent deployment names or connection strings unless the user has explicitly specified them. If they've previously connected to a MongoDB Atlas Local deployment using MCP, you can ask them if they want to reconnect using the same deployment.` + ); + } + + return hints.length > 0 + ? hints.join("\n") + : "Note to LLM: do not invent connection strings and explicitly ask the user to provide one. If they have previously connected to MongoDB using MCP, you can ask them if they want to reconnect using the same connection string."; + })(); const connectToolsNames = connectTools?.map((t) => `"${t.name}"`).join(", "); const additionalPromptForConnectivity: { type: "text"; text: string }[] = [];