From 2ad8fce28110a5b3d3d7696d0b7b8727149595a1 Mon Sep 17 00:00:00 2001 From: Luke Sanderson Date: Thu, 11 Sep 2025 10:42:53 +0100 Subject: [PATCH 1/6] Add createDeployment Tool --- .../atlasLocal/create/createDeployment.ts | 31 +++++++++++++++++++ src/tools/atlasLocal/tools.ts | 3 +- 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 src/tools/atlasLocal/create/createDeployment.ts diff --git a/src/tools/atlasLocal/create/createDeployment.ts b/src/tools/atlasLocal/create/createDeployment.ts new file mode 100644 index 000000000..fba6d547a --- /dev/null +++ b/src/tools/atlasLocal/create/createDeployment.ts @@ -0,0 +1,31 @@ +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 type { CreateDeploymentOptions, CreationSourceType } from "@mongodb-js-preview/atlas-local"; +import z from "zod"; + +export class CreateDeploymentTool extends AtlasLocalToolBase { + public name = "atlas-local-create-deployment"; + protected description = "Create a MongoDB Atlas local deployment"; + public operationType: OperationType = "create"; + protected argsShape = { + deploymentName: z.string().describe("Name of the deployment to create"), + }; + + protected async executeWithAtlasLocalClient( + client: Client, + { deploymentName }: ToolArgs + ): Promise { + const deploymentOptions: CreateDeploymentOptions = { + name: deploymentName, + creationSource: "MCPServer", + }; + // Create the deployment + await client.createDeployment(deploymentOptions); + + return { + content: [{ type: "text", text: `Deployment ${deploymentName} created.` }], + }; + } +} diff --git a/src/tools/atlasLocal/tools.ts b/src/tools/atlasLocal/tools.ts index 5284be1d2..655ae1dc7 100644 --- a/src/tools/atlasLocal/tools.ts +++ b/src/tools/atlasLocal/tools.ts @@ -1,4 +1,5 @@ import { DeleteDeploymentTool } from "./delete/deleteDeployment.js"; import { ListDeploymentsTool } from "./read/listDeployments.js"; +import { CreateDeploymentTool } from "./create/createDeployment.js"; -export const AtlasLocalTools = [ListDeploymentsTool, DeleteDeploymentTool]; +export const AtlasLocalTools = [ListDeploymentsTool, DeleteDeploymentTool, CreateDeploymentTool]; From 4c61ad3f35766adb4ee898b71fc9dfb7cf32b8c0 Mon Sep 17 00:00:00 2001 From: Luke Sanderson Date: Thu, 11 Sep 2025 12:30:35 +0100 Subject: [PATCH 2/6] Add create tests --- .../atlasLocal/create/createDeployment.ts | 17 +- .../atlas-local/createDeployment.test.ts | 219 ++++++++++++++++++ 2 files changed, 231 insertions(+), 5 deletions(-) create mode 100644 tests/integration/tools/atlas-local/createDeployment.test.ts diff --git a/src/tools/atlasLocal/create/createDeployment.ts b/src/tools/atlasLocal/create/createDeployment.ts index fba6d547a..8c8d10441 100644 --- a/src/tools/atlasLocal/create/createDeployment.ts +++ b/src/tools/atlasLocal/create/createDeployment.ts @@ -1,8 +1,7 @@ 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 type { CreateDeploymentOptions, CreationSourceType } from "@mongodb-js-preview/atlas-local"; +import type { Client, CreateDeploymentOptions, CreationSourceType } from "@mongodb-js-preview/atlas-local"; import z from "zod"; export class CreateDeploymentTool extends AtlasLocalToolBase { @@ -10,7 +9,7 @@ export class CreateDeploymentTool extends AtlasLocalToolBase { protected description = "Create a MongoDB Atlas local deployment"; public operationType: OperationType = "create"; protected argsShape = { - deploymentName: z.string().describe("Name of the deployment to create"), + deploymentName: z.string().describe("Name of the deployment to create").optional(), }; protected async executeWithAtlasLocalClient( @@ -19,13 +18,21 @@ export class CreateDeploymentTool extends AtlasLocalToolBase { ): Promise { const deploymentOptions: CreateDeploymentOptions = { name: deploymentName, - creationSource: "MCPServer", + creationSource: { + type: "MCPServer" as CreationSourceType, + source: "MCPServer", + }, }; // Create the deployment await client.createDeployment(deploymentOptions); + if (deploymentName) { + return { + content: [{ type: "text", text: `Deployment "${deploymentName}" created.` }], + }; + } return { - content: [{ type: "text", text: `Deployment ${deploymentName} created.` }], + content: [{ type: "text", text: `Deployment created.` }], }; } } diff --git a/tests/integration/tools/atlas-local/createDeployment.test.ts b/tests/integration/tools/atlas-local/createDeployment.test.ts new file mode 100644 index 000000000..efb71106e --- /dev/null +++ b/tests/integration/tools/atlas-local/createDeployment.test.ts @@ -0,0 +1,219 @@ +import { + defaultDriverOptions, + defaultTestConfig, + expectDefined, + getResponseElements, + setupIntegrationTest, + waitUntilMcpClientIsSet, +} from "../../helpers.js"; +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-create-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-create-deployment tool", async ({ signal }) => { + await waitUntilMcpClientIsSet(integration.mcpServer(), signal); + + const { tools } = await integration.mcpClient().listTools(); + const createDeployment = tools.find((tool) => tool.name === "atlas-local-create-deployment"); + expectDefined(createDeployment); + }); + + it.skipIf(!isMacOSInGitHubActions)( + "[MacOS in GitHub Actions] should not have the atlas-local-create-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 createDeployment = tools.find((tool) => tool.name === "atlas-local-create-deployment"); + expect(createDeployment).toBeUndefined(); + } + ); + + it.skipIf(isMacOSInGitHubActions)("should have correct metadata", async ({ signal }) => { + await waitUntilMcpClientIsSet(integration.mcpServer(), signal); + const { tools } = await integration.mcpClient().listTools(); + const createDeployment = tools.find((tool) => tool.name === "atlas-local-create-deployment"); + expectDefined(createDeployment); + expect(createDeployment.inputSchema.type).toBe("object"); + expectDefined(createDeployment.inputSchema.properties); + expect(createDeployment.inputSchema.properties).toHaveProperty("deploymentName"); + }); + + it.skipIf(isMacOSInGitHubActions).sequential( + "should create a deployment when calling the tool", + async ({ signal }) => { + await waitUntilMcpClientIsSet(integration.mcpServer(), signal); + + // Count the current number of deployments + const beforeResponse = await integration.mcpClient().callTool({ + name: "atlas-local-list-deployments", + arguments: {}, + }); + const beforeNumberOfDeployments = parseInt( + getResponseElements(beforeResponse.content)[0]?.text.match(/\d+/)?.[0] || "0", + 10 + ); + + // Create a deployment + const deploymentName = `test-deployment-${Date.now()}`; + deploymentNamesToCleanup.push(deploymentName); + await integration.mcpClient().callTool({ + name: "atlas-local-create-deployment", + arguments: { deploymentName }, + }); + + // Count the number of deployments after creating the deployment + const afterResponse = await integration.mcpClient().callTool({ + name: "atlas-local-list-deployments", + arguments: {}, + }); + const afterNumberOfDeployments = parseInt( + getResponseElements(afterResponse.content)[0]?.text.match(/\d+/)?.[0] || "0", + 10 + ); + // Check that the number of deployments has increased by 1 + expect(afterNumberOfDeployments).toBe(beforeNumberOfDeployments + 1); + } + ); + + it.skipIf(isMacOSInGitHubActions).sequential( + "should return an error when creating a deployment that already exists", + async ({ signal }) => { + 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 }, + }); + + // Try to create the same deployment again + const response = await integration.mcpClient().callTool({ + name: "atlas-local-create-deployment", + arguments: { deploymentName }, + }); + const elements = getResponseElements(response.content); + expect(elements.length).toBeGreaterThanOrEqual(1); + expect(elements[0]?.text).toContain("Container already exists: " + deploymentName); + } + ); + + it.skipIf(isMacOSInGitHubActions).sequential( + "should create a deployment with the correct name", + async ({ signal }) => { + 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 }, + }); + + // List the deployments + const response = await integration.mcpClient().callTool({ + name: "atlas-local-list-deployments", + arguments: {}, + }); + const elements = getResponseElements(response.content); + expect(elements.length).toBeGreaterThanOrEqual(1); + expect(elements[1]?.text).toContain(deploymentName); + expect(elements[1]?.text).toContain("Running"); + } + ); + + it.skipIf(isMacOSInGitHubActions).sequential( + "should create a deployment when name is not provided", + async ({ signal }) => { + await waitUntilMcpClientIsSet(integration.mcpServer(), signal); + + // Create a deployment + await integration.mcpClient().callTool({ + name: "atlas-local-create-deployment", + arguments: {}, + }); + + // List the deployments + const response = await integration.mcpClient().callTool({ + name: "atlas-local-list-deployments", + arguments: {}, + }); + const elements = getResponseElements(response.content); + expect(elements.length).toBeGreaterThanOrEqual(1); + // Random name starts with local and a number + const deploymentName = elements[1]?.text.match(/local\d+/)?.[0]; + expectDefined(deploymentName); + deploymentNamesToCleanup.push(deploymentName); + expect(elements[1]?.text).toContain("Running"); + } + ); + + it.skipIf(isMacOSInGitHubActions).sequential( + "should delete 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 }, + }); + + // Count the current number of deployments + const beforeResponse = await integration.mcpClient().callTool({ + name: "atlas-local-list-deployments", + arguments: {}, + }); + const beforeNumberOfDeployments = parseInt( + getResponseElements(beforeResponse.content)[0]?.text.match(/\d+/)?.[0] || "0", + 10 + ); + + // Delete the deployment + await integration.mcpClient().callTool({ + name: "atlas-local-delete-deployment", + arguments: { deploymentName }, + }); + + // Count the number of deployments after deleting the deployment + const afterResponse = await integration.mcpClient().callTool({ + name: "atlas-local-list-deployments", + arguments: {}, + }); + const afterNumberOfDeployments = parseInt( + getResponseElements(afterResponse.content)[0]?.text.match(/\d+/)?.[0] || "0", + 10 + ); + // Check that the number of deployments has decreased by 1 + expect(afterNumberOfDeployments).toBe(beforeNumberOfDeployments - 1); + } + ); +}); From 6ee6636577284081d2b684543b0f50ef46a00728 Mon Sep 17 00:00:00 2001 From: Luke Sanderson Date: Thu, 11 Sep 2025 13:50:23 +0100 Subject: [PATCH 3/6] Allow test to run concurrently --- .../atlas-local/createDeployment.test.ts | 200 +++++++----------- .../atlas-local/deleteDeployment.test.ts | 32 +++ 2 files changed, 107 insertions(+), 125 deletions(-) diff --git a/tests/integration/tools/atlas-local/createDeployment.test.ts b/tests/integration/tools/atlas-local/createDeployment.test.ts index efb71106e..8284193db 100644 --- a/tests/integration/tools/atlas-local/createDeployment.test.ts +++ b/tests/integration/tools/atlas-local/createDeployment.test.ts @@ -64,44 +64,37 @@ describe("atlas-local-create-deployment", () => { expect(createDeployment.inputSchema.properties).toHaveProperty("deploymentName"); }); - it.skipIf(isMacOSInGitHubActions).sequential( - "should create a deployment when calling the tool", - async ({ signal }) => { - await waitUntilMcpClientIsSet(integration.mcpServer(), signal); - - // Count the current number of deployments - const beforeResponse = await integration.mcpClient().callTool({ - name: "atlas-local-list-deployments", - arguments: {}, - }); - const beforeNumberOfDeployments = parseInt( - getResponseElements(beforeResponse.content)[0]?.text.match(/\d+/)?.[0] || "0", - 10 - ); - - // Create a deployment - const deploymentName = `test-deployment-${Date.now()}`; - deploymentNamesToCleanup.push(deploymentName); - await integration.mcpClient().callTool({ - name: "atlas-local-create-deployment", - arguments: { deploymentName }, - }); - - // Count the number of deployments after creating the deployment - const afterResponse = await integration.mcpClient().callTool({ - name: "atlas-local-list-deployments", - arguments: {}, - }); - const afterNumberOfDeployments = parseInt( - getResponseElements(afterResponse.content)[0]?.text.match(/\d+/)?.[0] || "0", - 10 - ); - // Check that the number of deployments has increased by 1 - expect(afterNumberOfDeployments).toBe(beforeNumberOfDeployments + 1); - } - ); + it.skipIf(isMacOSInGitHubActions)("should create a deployment when calling the tool", async ({ signal }) => { + await waitUntilMcpClientIsSet(integration.mcpServer(), signal); + const deploymentName = `test-deployment-${Date.now()}`; + + // Check that deployment doesn't exist before creation + const beforeResponse = await integration.mcpClient().callTool({ + name: "atlas-local-list-deployments", + arguments: {}, + }); + const beforeElements = getResponseElements(beforeResponse.content); + expect(beforeElements[1]?.text ?? "").not.toContain(deploymentName); + + // Create a deployment + deploymentNamesToCleanup.push(deploymentName); + await integration.mcpClient().callTool({ + name: "atlas-local-create-deployment", + arguments: { deploymentName }, + }); + + // Check that deployment exists after creation + const afterResponse = await integration.mcpClient().callTool({ + name: "atlas-local-list-deployments", + arguments: {}, + }); + + const afterElements = getResponseElements(afterResponse.content); + expect(afterElements.length).toBeGreaterThanOrEqual(1); + expect(afterElements[1]?.text).toContain(deploymentName); + }); - it.skipIf(isMacOSInGitHubActions).sequential( + it.skipIf(isMacOSInGitHubActions)( "should return an error when creating a deployment that already exists", async ({ signal }) => { await waitUntilMcpClientIsSet(integration.mcpServer(), signal); @@ -125,95 +118,52 @@ describe("atlas-local-create-deployment", () => { } ); - it.skipIf(isMacOSInGitHubActions).sequential( - "should create a deployment with the correct name", - async ({ signal }) => { - 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 }, - }); - - // List the deployments - const response = await integration.mcpClient().callTool({ - name: "atlas-local-list-deployments", - arguments: {}, - }); - const elements = getResponseElements(response.content); - expect(elements.length).toBeGreaterThanOrEqual(1); - expect(elements[1]?.text).toContain(deploymentName); - expect(elements[1]?.text).toContain("Running"); - } - ); - - it.skipIf(isMacOSInGitHubActions).sequential( - "should create a deployment when name is not provided", - async ({ signal }) => { - await waitUntilMcpClientIsSet(integration.mcpServer(), signal); - - // Create a deployment - await integration.mcpClient().callTool({ - name: "atlas-local-create-deployment", - arguments: {}, - }); - - // List the deployments - const response = await integration.mcpClient().callTool({ - name: "atlas-local-list-deployments", - arguments: {}, - }); - const elements = getResponseElements(response.content); - expect(elements.length).toBeGreaterThanOrEqual(1); - // Random name starts with local and a number - const deploymentName = elements[1]?.text.match(/local\d+/)?.[0]; - expectDefined(deploymentName); - deploymentNamesToCleanup.push(deploymentName); - expect(elements[1]?.text).toContain("Running"); - } - ); - - it.skipIf(isMacOSInGitHubActions).sequential( - "should delete 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 }, - }); + it.skipIf(isMacOSInGitHubActions)("should create a deployment with the correct name", async ({ signal }) => { + await waitUntilMcpClientIsSet(integration.mcpServer(), signal); - // Count the current number of deployments - const beforeResponse = await integration.mcpClient().callTool({ - name: "atlas-local-list-deployments", - arguments: {}, - }); - const beforeNumberOfDeployments = parseInt( - getResponseElements(beforeResponse.content)[0]?.text.match(/\d+/)?.[0] || "0", - 10 - ); + // Create a deployment + const deploymentName = `test-deployment-${Date.now()}`; + deploymentNamesToCleanup.push(deploymentName); + await integration.mcpClient().callTool({ + name: "atlas-local-create-deployment", + arguments: { deploymentName }, + }); + + // List the deployments + const response = await integration.mcpClient().callTool({ + name: "atlas-local-list-deployments", + arguments: {}, + }); + const elements = getResponseElements(response.content); + + expect(elements.length).toBeGreaterThanOrEqual(1); + expect(elements[1]?.text).toContain(deploymentName); + expect(elements[1]?.text).toContain("Running"); + }); - // Delete the deployment - await integration.mcpClient().callTool({ - name: "atlas-local-delete-deployment", - arguments: { deploymentName }, - }); + it.skipIf(isMacOSInGitHubActions)("should create a deployment when name is not provided", async ({ signal }) => { + await waitUntilMcpClientIsSet(integration.mcpServer(), signal); - // Count the number of deployments after deleting the deployment - const afterResponse = await integration.mcpClient().callTool({ - name: "atlas-local-list-deployments", - arguments: {}, - }); - const afterNumberOfDeployments = parseInt( - getResponseElements(afterResponse.content)[0]?.text.match(/\d+/)?.[0] || "0", - 10 - ); - // Check that the number of deployments has decreased by 1 - expect(afterNumberOfDeployments).toBe(beforeNumberOfDeployments - 1); - } - ); + // Create a deployment + await integration.mcpClient().callTool({ + name: "atlas-local-create-deployment", + arguments: {}, + }); + + // List the deployments + const response = await integration.mcpClient().callTool({ + name: "atlas-local-list-deployments", + arguments: {}, + }); + + // Check the deployment has been created + const elements = getResponseElements(response.content); + expect(elements.length).toBeGreaterThanOrEqual(1); + + // Random name starts with local and a number + const deploymentName = elements[1]?.text.match(/local\d+/)?.[0]; + expectDefined(deploymentName); + deploymentNamesToCleanup.push(deploymentName); + expect(elements[1]?.text).toContain("Running"); + }); }); diff --git a/tests/integration/tools/atlas-local/deleteDeployment.test.ts b/tests/integration/tools/atlas-local/deleteDeployment.test.ts index 87a309182..1f72a1fec 100644 --- a/tests/integration/tools/atlas-local/deleteDeployment.test.ts +++ b/tests/integration/tools/atlas-local/deleteDeployment.test.ts @@ -64,4 +64,36 @@ describe("atlas-local-delete-deployment", () => { ); } ); + + it.skipIf(isMacOSInGitHubActions)("should delete 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 }, + }); + + // Check that deployment exists before deletion + const beforeResponse = await integration.mcpClient().callTool({ + name: "atlas-local-list-deployments", + arguments: {}, + }); + const beforeElements = getResponseElements(beforeResponse.content); + expect(beforeElements[1]?.text).toContain(deploymentName); + + // Delete the deployment + await integration.mcpClient().callTool({ + name: "atlas-local-delete-deployment", + arguments: { deploymentName }, + }); + + // Count the number of deployments after deleting the deployment + const afterResponse = await integration.mcpClient().callTool({ + name: "atlas-local-list-deployments", + arguments: {}, + }); + const afterElements = getResponseElements(afterResponse.content); + expect(afterElements[1]?.text ?? "").not.toContain(deploymentName); + }); }); From 1a3bffa945b72f93c2dbbde674dfd95791ab9bf5 Mon Sep 17 00:00:00 2001 From: Luke Sanderson Date: Thu, 11 Sep 2025 14:01:50 +0100 Subject: [PATCH 4/6] Add expect default for empty list of deployments --- .../tools/atlas-local/createDeployment.test.ts | 9 +++++---- .../tools/atlas-local/deleteDeployment.test.ts | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/integration/tools/atlas-local/createDeployment.test.ts b/tests/integration/tools/atlas-local/createDeployment.test.ts index 8284193db..0dbda3074 100644 --- a/tests/integration/tools/atlas-local/createDeployment.test.ts +++ b/tests/integration/tools/atlas-local/createDeployment.test.ts @@ -74,6 +74,7 @@ describe("atlas-local-create-deployment", () => { arguments: {}, }); const beforeElements = getResponseElements(beforeResponse.content); + expect(beforeElements.length).toBeGreaterThanOrEqual(1); expect(beforeElements[1]?.text ?? "").not.toContain(deploymentName); // Create a deployment @@ -91,7 +92,7 @@ describe("atlas-local-create-deployment", () => { const afterElements = getResponseElements(afterResponse.content); expect(afterElements.length).toBeGreaterThanOrEqual(1); - expect(afterElements[1]?.text).toContain(deploymentName); + expect(afterElements[1]?.text ?? "").toContain(deploymentName); }); it.skipIf(isMacOSInGitHubActions)( @@ -137,8 +138,8 @@ describe("atlas-local-create-deployment", () => { const elements = getResponseElements(response.content); expect(elements.length).toBeGreaterThanOrEqual(1); - expect(elements[1]?.text).toContain(deploymentName); - expect(elements[1]?.text).toContain("Running"); + expect(elements[1]?.text ?? "").toContain(deploymentName); + expect(elements[1]?.text ?? "").toContain("Running"); }); it.skipIf(isMacOSInGitHubActions)("should create a deployment when name is not provided", async ({ signal }) => { @@ -164,6 +165,6 @@ describe("atlas-local-create-deployment", () => { const deploymentName = elements[1]?.text.match(/local\d+/)?.[0]; expectDefined(deploymentName); deploymentNamesToCleanup.push(deploymentName); - expect(elements[1]?.text).toContain("Running"); + expect(elements[1]?.text ?? "").toContain("Running"); }); }); diff --git a/tests/integration/tools/atlas-local/deleteDeployment.test.ts b/tests/integration/tools/atlas-local/deleteDeployment.test.ts index 1f72a1fec..6956da91f 100644 --- a/tests/integration/tools/atlas-local/deleteDeployment.test.ts +++ b/tests/integration/tools/atlas-local/deleteDeployment.test.ts @@ -80,7 +80,8 @@ describe("atlas-local-delete-deployment", () => { arguments: {}, }); const beforeElements = getResponseElements(beforeResponse.content); - expect(beforeElements[1]?.text).toContain(deploymentName); + expect(beforeElements.length).toBeGreaterThanOrEqual(1); + expect(beforeElements[1]?.text ?? "").toContain(deploymentName); // Delete the deployment await integration.mcpClient().callTool({ From 2e02b05d9a9032786a05a6e1dd48582d6af830a5 Mon Sep 17 00:00:00 2001 From: Luke Sanderson Date: Fri, 12 Sep 2025 12:26:56 +0100 Subject: [PATCH 5/6] Change createDeployment to return created deployment name --- package.json | 2 +- .../atlasLocal/create/createDeployment.ts | 14 +++++------ .../atlas-local/createDeployment.test.ts | 25 +++++++++++++------ 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 352fb3dc9..58004422c 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.2", + "@mongodb-js-preview/atlas-local": "^0.0.0-preview.3", "kerberos": "^2.2.2" } } diff --git a/src/tools/atlasLocal/create/createDeployment.ts b/src/tools/atlasLocal/create/createDeployment.ts index 8c8d10441..17cf26dab 100644 --- a/src/tools/atlasLocal/create/createDeployment.ts +++ b/src/tools/atlasLocal/create/createDeployment.ts @@ -24,15 +24,15 @@ export class CreateDeploymentTool extends AtlasLocalToolBase { }, }; // Create the deployment - await client.createDeployment(deploymentOptions); + const deployment = await client.createDeployment(deploymentOptions); - if (deploymentName) { - return { - content: [{ type: "text", text: `Deployment "${deploymentName}" created.` }], - }; - } return { - content: [{ type: "text", text: `Deployment created.` }], + content: [ + { + type: "text", + text: `Deployment with container ID "${deployment.containerId}" and name "${deployment.name}" created.`, + }, + ], }; } } diff --git a/tests/integration/tools/atlas-local/createDeployment.test.ts b/tests/integration/tools/atlas-local/createDeployment.test.ts index 0dbda3074..90dff002a 100644 --- a/tests/integration/tools/atlas-local/createDeployment.test.ts +++ b/tests/integration/tools/atlas-local/createDeployment.test.ts @@ -125,11 +125,16 @@ describe("atlas-local-create-deployment", () => { // Create a deployment const deploymentName = `test-deployment-${Date.now()}`; deploymentNamesToCleanup.push(deploymentName); - await integration.mcpClient().callTool({ + const createResponse = await integration.mcpClient().callTool({ name: "atlas-local-create-deployment", arguments: { deploymentName }, }); + // Check the response contains the deployment name + const createElements = getResponseElements(createResponse.content); + expect(createElements.length).toBeGreaterThanOrEqual(1); + expect(createElements[0]?.text).toContain(deploymentName); + // List the deployments const response = await integration.mcpClient().callTool({ name: "atlas-local-list-deployments", @@ -146,11 +151,21 @@ describe("atlas-local-create-deployment", () => { await waitUntilMcpClientIsSet(integration.mcpServer(), signal); // Create a deployment - await integration.mcpClient().callTool({ + const createResponse = await integration.mcpClient().callTool({ name: "atlas-local-create-deployment", arguments: {}, }); + // Check the response contains the deployment name + const createElements = getResponseElements(createResponse.content); + expect(createElements.length).toBeGreaterThanOrEqual(1); + + // Extract the deployment name from the response + // The name should be in the format local + const deploymentName = createElements[0]?.text.match(/local\d+/)?.[0]; + expectDefined(deploymentName); + deploymentNamesToCleanup.push(deploymentName); + // List the deployments const response = await integration.mcpClient().callTool({ name: "atlas-local-list-deployments", @@ -160,11 +175,7 @@ describe("atlas-local-create-deployment", () => { // Check the deployment has been created const elements = getResponseElements(response.content); expect(elements.length).toBeGreaterThanOrEqual(1); - - // Random name starts with local and a number - const deploymentName = elements[1]?.text.match(/local\d+/)?.[0]; - expectDefined(deploymentName); - deploymentNamesToCleanup.push(deploymentName); + expect(elements[1]?.text ?? "").toContain(deploymentName); expect(elements[1]?.text ?? "").toContain("Running"); }); }); From 5656f1ea5644f4eb55a8a16a1a5dae83c2a23dc2 Mon Sep 17 00:00:00 2001 From: Luke Sanderson Date: Fri, 12 Sep 2025 12:31:06 +0100 Subject: [PATCH 6/6] update package-lock.json --- package-lock.json | 48 +++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8ae6845ac..d5fdf990f 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.2", + "@mongodb-js-preview/atlas-local": "^0.0.0-preview.3", "kerberos": "^2.2.2" } }, @@ -2050,26 +2050,26 @@ } }, "node_modules/@mongodb-js-preview/atlas-local": { - "version": "0.0.0-preview.2", - "resolved": "https://registry.npmjs.org/@mongodb-js-preview/atlas-local/-/atlas-local-0.0.0-preview.2.tgz", - "integrity": "sha512-gDU+xL3p//aSfGqjr3Zth8rlfjXXiu8D9+K7Q8s4Z83+0V0TciT7x4hVQYYtVdsny4MxHokTr/6PnG83z0yHIw==", + "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==", "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.2", - "@mongodb-js-preview/atlas-local-darwin-x64": "0.0.0-preview.2", - "@mongodb-js-preview/atlas-local-linux-arm64-gnu": "0.0.0-preview.2", - "@mongodb-js-preview/atlas-local-linux-x64-gnu": "0.0.0-preview.2", - "@mongodb-js-preview/atlas-local-win32-x64-msvc": "0.0.0-preview.2" + "@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" } }, "node_modules/@mongodb-js-preview/atlas-local-darwin-arm64": { - "version": "0.0.0-preview.2", - "resolved": "https://registry.npmjs.org/@mongodb-js-preview/atlas-local-darwin-arm64/-/atlas-local-darwin-arm64-0.0.0-preview.2.tgz", - "integrity": "sha512-aqiMeXCjawUYIP63y4buP+oRgg5jJ4g9HTOU7nMVZPO4aidLAMbIMgZVtolwplqofRlzxB9V6g4TiXS+Ksr8LA==", + "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==", "cpu": [ "arm64" ], @@ -2083,9 +2083,9 @@ } }, "node_modules/@mongodb-js-preview/atlas-local-darwin-x64": { - "version": "0.0.0-preview.2", - "resolved": "https://registry.npmjs.org/@mongodb-js-preview/atlas-local-darwin-x64/-/atlas-local-darwin-x64-0.0.0-preview.2.tgz", - "integrity": "sha512-0wKAf+XddBHYqDJ9ofnXUKZhOKv2ruqv1Ev1M+mksIiX+b321yz3K2HCDRjYMaLva75QYxbBy2csBoxSUBwbmA==", + "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==", "cpu": [ "x64" ], @@ -2099,9 +2099,9 @@ } }, "node_modules/@mongodb-js-preview/atlas-local-linux-arm64-gnu": { - "version": "0.0.0-preview.2", - "resolved": "https://registry.npmjs.org/@mongodb-js-preview/atlas-local-linux-arm64-gnu/-/atlas-local-linux-arm64-gnu-0.0.0-preview.2.tgz", - "integrity": "sha512-6qaA64ffmKbnjfDFo9s+jESSWOJO2v85HYlEdZlCj//7gWKFIN+5sDqCYHmSVlLUFGJAWSsQcJVhai2ojhQyjQ==", + "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==", "cpu": [ "arm64" ], @@ -2115,9 +2115,9 @@ } }, "node_modules/@mongodb-js-preview/atlas-local-linux-x64-gnu": { - "version": "0.0.0-preview.2", - "resolved": "https://registry.npmjs.org/@mongodb-js-preview/atlas-local-linux-x64-gnu/-/atlas-local-linux-x64-gnu-0.0.0-preview.2.tgz", - "integrity": "sha512-Y/AjlvP6rJqxByygycS0jtmNphsEjNcVFI2+uEFlY/QqU8I74RIYkFArJSuNJjv5vBh8/i+bw10gDwYlWVEuYA==", + "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==", "cpu": [ "x64" ], @@ -2131,9 +2131,9 @@ } }, "node_modules/@mongodb-js-preview/atlas-local-win32-x64-msvc": { - "version": "0.0.0-preview.2", - "resolved": "https://registry.npmjs.org/@mongodb-js-preview/atlas-local-win32-x64-msvc/-/atlas-local-win32-x64-msvc-0.0.0-preview.2.tgz", - "integrity": "sha512-5xo3k+o/4m6P3CkMI8IPebVxdxVVSWwc+amMISuLq7DPFKmhU5m9gDiXfeg/tS67W1+HIHlUFeuCbcOpnIqRZQ==", + "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==", "cpu": [ "x64" ],