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.3",
"@mongodb-js-preview/atlas-local": "^0.0.0-preview.6",
"kerberos": "^2.2.2"
}
}
27 changes: 22 additions & 5 deletions src/common/connectionErrorHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +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 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");

const llmConnectHint = ((): string => {
const hints: 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 }[] = [];
Expand Down
14 changes: 14 additions & 0 deletions src/tools/atlasLocal/atlasLocalTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ please log a ticket here: https://github.com/mongodb-js/mongodb-mcp-server/issue
args: ToolArgs<typeof this.argsShape>
): Promise<CallToolResult> | 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);
Expand Down
34 changes: 34 additions & 0 deletions src/tools/atlasLocal/connect/connectDeployment.ts
Original file line number Diff line number Diff line change
@@ -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 = {
deploymentName: z.string().describe("Name of the deployment to connect to"),
};

protected async executeWithAtlasLocalClient(
client: Client,
{ deploymentName }: ToolArgs<typeof this.argsShape>
): Promise<CallToolResult> {
// Get the connection string for the deployment
const connectionString = await client.getConnectionString(deploymentName);

// Connect to the deployment
await this.session.connectToMongoDB({ connectionString });

return {
content: [
{
type: "text",
text: `Successfully connected to Atlas Local deployment "${deploymentName}".`,
},
],
};
}
}
3 changes: 2 additions & 1 deletion src/tools/atlasLocal/tools.ts
Original file line number Diff line number Diff line change
@@ -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];
70 changes: 70 additions & 0 deletions tests/accuracy/connectDeployment.test.ts
Original file line number Diff line number Diff line change
@@ -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",
},
},
],
},
]);
114 changes: 114 additions & 0 deletions tests/integration/tools/atlas-local/connectDeployment.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +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.skipIf(isMacOSInGitHubActions)("atlas-local-connect-deployment", () => {
beforeEach(async ({ signal }) => {
await waitUntilMcpClientIsSet(integration.mcpServer(), signal);
});

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("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);
});

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.`
);
});
});

describe.skipIf(isMacOSInGitHubActions)("atlas-local-connect-deployment with deployments", () => {
let deploymentName: string = "";
let deploymentNamesToCleanup: string[] = [];

beforeEach(async ({ signal }) => {
await waitUntilMcpClientIsSet(integration.mcpServer(), signal);

// 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: { deploymentName },
});
const elements = getResponseElements(response.content);
expect(elements.length).toBeGreaterThanOrEqual(1);
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();
});
});
Loading
Loading