Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 24 additions & 24 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
38 changes: 38 additions & 0 deletions src/tools/atlasLocal/create/createDeployment.ts
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.`,
},
],
};
}
}
3 changes: 2 additions & 1 deletion src/tools/atlasLocal/tools.ts
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];
181 changes: 181 additions & 0 deletions tests/integration/tools/atlas-local/createDeployment.test.ts
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 () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to run this on isMacOSInGitHubActions, can we skip it here too?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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:

if (isMacOSInGitHubActions) {
    return;
}

But since the deploymentToCleanup array will always be empty it will be the same execution time

Copy link
Member

Choose a reason for hiding this comment

The 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

View workflow job for this annotation

GitHub Actions / Run MongoDB tests (windows-latest)

tests/integration/tools/atlas-local/createDeployment.test.ts > atlas-local-create-deployment > should create a deployment when calling the tool

AssertionError: expected '' to contain 'test-deployment-1757677001586' - Expected + Received - test-deployment-1757677001586 ❯ tests/integration/tools/atlas-local/createDeployment.test.ts:95:46
});

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

View workflow job for this annotation

GitHub Actions / Run MongoDB tests (windows-latest)

tests/integration/tools/atlas-local/createDeployment.test.ts > atlas-local-create-deployment > should return an error when creating a deployment that already exists

AssertionError: expected 'Error running atlas-local-create-depl…' to contain 'Container already exists: test-deploy…' - Expected + Received - Container already exists: test-deployment-1757677003059 + Error running atlas-local-create-deployment: create deployment + + Caused by: + 0: Failed to pull image: Docker stream error: no matching manifest for windows/amd64 10.0.20348 in the manifest list entries + 1: Docker stream error: no matching manifest for windows/amd64 10.0.20348 in the manifest list entries ❯ tests/integration/tools/atlas-local/createDeployment.test.ts:118:39
}
);

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

View workflow job for this annotation

GitHub Actions / Run MongoDB tests (windows-latest)

tests/integration/tools/atlas-local/createDeployment.test.ts > atlas-local-create-deployment > should create a deployment with the correct name

AssertionError: expected 'Error running atlas-local-create-depl…' to contain 'test-deployment-1757677004142' - Expected + Received - test-deployment-1757677004142 + Error running atlas-local-create-deployment: create deployment + + Caused by: + 0: Failed to pull image: Docker stream error: no matching manifest for windows/amd64 10.0.20348 in the manifest list entries + 1: Docker stream error: no matching manifest for windows/amd64 10.0.20348 in the manifest list entries ❯ tests/integration/tools/atlas-local/createDeployment.test.ts:136:41

// 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");
});
});
33 changes: 33 additions & 0 deletions tests/integration/tools/atlas-local/deleteDeployment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,37 @@
);
}
);

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.length).toBeGreaterThanOrEqual(1);
expect(beforeElements[1]?.text ?? "").toContain(deploymentName);

Check failure on line 84 in tests/integration/tools/atlas-local/deleteDeployment.test.ts

View workflow job for this annotation

GitHub Actions / Run MongoDB tests (windows-latest)

tests/integration/tools/atlas-local/deleteDeployment.test.ts > atlas-local-delete-deployment > should delete a deployment when calling the tool

AssertionError: expected '' to contain 'test-deployment-1757677015374' - Expected + Received - test-deployment-1757677015374 ❯ tests/integration/tools/atlas-local/deleteDeployment.test.ts:84:47

// 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);
});
});
Loading