- 
                Notifications
    You must be signed in to change notification settings 
- Fork 152
feat(atlas-local): Add Atlas Local Create Deployment tool #546
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2ad8fce
              4c61ad3
              6ee6636
              1a3bffa
              2e02b05
              5656f1e
              File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; | ||
| import { AtlasLocalToolBase } from "../atlasLocalTool.js"; | ||
| import type { OperationType, ToolArgs } from "../../tool.js"; | ||
| import type { Client, 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").optional(), | ||
| }; | ||
|  | ||
| protected async executeWithAtlasLocalClient( | ||
| client: Client, | ||
| { deploymentName }: ToolArgs<typeof this.argsShape> | ||
| ): Promise<CallToolResult> { | ||
| const deploymentOptions: CreateDeploymentOptions = { | ||
| name: deploymentName, | ||
| creationSource: { | ||
| type: "MCPServer" as CreationSourceType, | ||
| source: "MCPServer", | ||
| }, | ||
| }; | ||
| // Create the deployment | ||
| const deployment = await client.createDeployment(deploymentOptions); | ||
|  | ||
| return { | ||
| content: [ | ||
| { | ||
| type: "text", | ||
| text: `Deployment with container ID "${deployment.containerId}" and name "${deployment.name}" created.`, | ||
| }, | ||
| ], | ||
| }; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -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]; | 
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,181 @@ | ||
| 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 () => { | ||
| There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need to run this on  There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see a way to do the skipIf(isMacOSInGitHubActions) or similar for an afterEach. This leaves adding something like this in the afterEach function: But since the deploymentToCleanup array will always be empty it will be the same execution time There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense to not skip it then! | ||
| // 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)("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.length).toBeGreaterThanOrEqual(1); | ||
| 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); | ||
| Check failure on line 95 in tests/integration/tools/atlas-local/createDeployment.test.ts 
     | ||
| }); | ||
|  | ||
| it.skipIf(isMacOSInGitHubActions)( | ||
| "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); | ||
| Check failure on line 118 in tests/integration/tools/atlas-local/createDeployment.test.ts 
     | ||
| } | ||
| ); | ||
|  | ||
| it.skipIf(isMacOSInGitHubActions)("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); | ||
| 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); | ||
| Check failure on line 136 in tests/integration/tools/atlas-local/createDeployment.test.ts 
     | ||
|  | ||
| // 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)("should create a deployment when name is not provided", async ({ signal }) => { | ||
| await waitUntilMcpClientIsSet(integration.mcpServer(), signal); | ||
|  | ||
| // Create a deployment | ||
| 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<number> | ||
| 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", | ||
| arguments: {}, | ||
| }); | ||
|  | ||
| // Check the deployment has been created | ||
| const elements = getResponseElements(response.content); | ||
| expect(elements.length).toBeGreaterThanOrEqual(1); | ||
| expect(elements[1]?.text ?? "").toContain(deploymentName); | ||
| expect(elements[1]?.text ?? "").toContain("Running"); | ||
| }); | ||
| }); | ||
Uh oh!
There was an error while loading. Please reload this page.