Skip to content

feat: hide atlas tools when not configured #96

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

Merged
merged 8 commits into from
Apr 23, 2025
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ You may experiment asking `Can you connect to my mongodb instance?`.
- `atlas-list-db-users` - List MongoDB Atlas database users
- `atlas-create-db-user` - List MongoDB Atlas database users

NOTE: atlas tools are only available when you set credentials on [configuration](#configuration) section.

#### MongoDB Database Tools

- `connect` - Connect to a MongoDB instance
Expand Down
10 changes: 6 additions & 4 deletions src/common/atlas/apiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import { paths, operations } from "./openapi.js";

const ATLAS_API_VERSION = "2025-03-12";

export interface ApiClientCredentials {
clientId: string;
clientSecret: string;
}

export interface ApiClientOptions {
credentials?: {
clientId: string;
clientSecret: string;
};
credentials?: ApiClientCredentials;
baseUrl?: string;
userAgent?: string;
}
Expand Down
31 changes: 14 additions & 17 deletions src/session.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,24 @@
import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver";
import { ApiClient } from "./common/atlas/apiClient.js";
import { ApiClient, ApiClientCredentials } from "./common/atlas/apiClient.js";
import config from "./config.js";

export class Session {
serviceProvider?: NodeDriverServiceProvider;
apiClient?: ApiClient;
apiClient: ApiClient;

ensureAuthenticated(): asserts this is { apiClient: ApiClient } {
if (!this.apiClient) {
if (!config.apiClientId || !config.apiClientSecret) {
throw new Error(
"Not authenticated make sure to configure MCP server with MDB_MCP_API_CLIENT_ID and MDB_MCP_API_CLIENT_SECRET environment variables."
);
}
constructor() {
const credentials: ApiClientCredentials | undefined =
config.apiClientId && config.apiClientSecret
? {
clientId: config.apiClientId,
clientSecret: config.apiClientSecret,
}
: undefined;

this.apiClient = new ApiClient({
baseUrl: config.apiBaseUrl,
credentials: {
clientId: config.apiClientId,
clientSecret: config.apiClientSecret,
},
});
}
this.apiClient = new ApiClient({
baseUrl: config.apiBaseUrl,
credentials,
});
}

async close(): Promise<void> {
Expand Down
8 changes: 8 additions & 0 deletions src/tools/atlas/atlasTool.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import { ToolBase, ToolCategory } from "../tool.js";
import { Session } from "../../session.js";
import config from "../../config.js";

export abstract class AtlasToolBase extends ToolBase {
constructor(protected readonly session: Session) {
super(session);
}

protected category: ToolCategory = "atlas";

protected verifyAllowed(): boolean {
if (!config.apiClientId || !config.apiClientSecret) {
return false;
}
return super.verifyAllowed();
}
}
2 changes: 0 additions & 2 deletions src/tools/atlas/createAccessList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ export class CreateAccessListTool extends AtlasToolBase {
comment,
currentIpAddress,
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
this.session.ensureAuthenticated();

if (!ipAddresses?.length && !cidrBlocks?.length && !currentIpAddress) {
throw new Error("One of ipAddresses, cidrBlocks, currentIpAddress must be provided.");
}
Expand Down
2 changes: 0 additions & 2 deletions src/tools/atlas/createDBUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ export class CreateDBUserTool extends AtlasToolBase {
roles,
clusters,
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
this.session.ensureAuthenticated();

const input = {
groupId: projectId,
awsIAMType: "NONE",
Expand Down
2 changes: 0 additions & 2 deletions src/tools/atlas/createFreeCluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ export class CreateFreeClusterTool extends AtlasToolBase {
};

protected async execute({ projectId, name, region }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
this.session.ensureAuthenticated();

const input = {
groupId: projectId,
name,
Expand Down
1 change: 0 additions & 1 deletion src/tools/atlas/createProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export class CreateProjectTool extends AtlasToolBase {
};

protected async execute({ projectName, organizationId }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
this.session.ensureAuthenticated();
let assumedOrg = false;

if (!projectName) {
Expand Down
2 changes: 0 additions & 2 deletions src/tools/atlas/inspectAccessList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ export class InspectAccessListTool extends AtlasToolBase {
};

protected async execute({ projectId }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
this.session.ensureAuthenticated();

const accessList = await this.session.apiClient.listProjectIpAccessLists({
params: {
path: {
Expand Down
2 changes: 0 additions & 2 deletions src/tools/atlas/inspectCluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ export class InspectClusterTool extends AtlasToolBase {
};

protected async execute({ projectId, clusterName }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
this.session.ensureAuthenticated();

const cluster = await this.session.apiClient.getCluster({
params: {
path: {
Expand Down
2 changes: 0 additions & 2 deletions src/tools/atlas/listClusters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ export class ListClustersTool extends AtlasToolBase {
};

protected async execute({ projectId }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
this.session.ensureAuthenticated();

if (!projectId) {
const data = await this.session.apiClient.listClustersForAllProjects();

Expand Down
2 changes: 0 additions & 2 deletions src/tools/atlas/listDBUsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ export class ListDBUsersTool extends AtlasToolBase {
};

protected async execute({ projectId }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
this.session.ensureAuthenticated();

const data = await this.session.apiClient.listDatabaseUsers({
params: {
path: {
Expand Down
2 changes: 0 additions & 2 deletions src/tools/atlas/listOrgs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ export class ListOrganizationsTool extends AtlasToolBase {
protected argsShape = {};

protected async execute(): Promise<CallToolResult> {
this.session.ensureAuthenticated();

const data = await this.session.apiClient.listOrganizations();

if (!data?.results?.length) {
Expand Down
2 changes: 0 additions & 2 deletions src/tools/atlas/listProjects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ export class ListProjectsTool extends AtlasToolBase {
};

protected async execute({ orgId }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
this.session.ensureAuthenticated();

const data = orgId
? await this.session.apiClient.listOrganizationProjects({
params: {
Expand Down
2 changes: 1 addition & 1 deletion src/tools/tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export abstract class ToolBase {
}

// Checks if a tool is allowed to run based on the config
private verifyAllowed(): boolean {
protected verifyAllowed(): boolean {
let errorClarification: string | undefined;
if (config.disabledTools.includes(this.category)) {
errorClarification = `its category, \`${this.category}\`,`;
Expand Down
11 changes: 4 additions & 7 deletions tests/integration/tools/atlas/accessLists.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import { Session } from "../../../../src/session.js";
import { describeAtlas, withProject } from "./atlasHelpers.js";

function generateRandomIp() {
Expand All @@ -17,20 +16,18 @@ describeAtlas("ip access lists", (integration) => {
const values = [...ips, ...cidrBlocks];

beforeAll(async () => {
const session: Session = integration.mcpServer().session;
session.ensureAuthenticated();
const ipInfo = await session.apiClient.getIpInfo();
const apiClient = integration.mcpServer().session.apiClient;
const ipInfo = await apiClient.getIpInfo();
values.push(ipInfo.currentIpv4Address);
});

afterAll(async () => {
const session: Session = integration.mcpServer().session;
session.ensureAuthenticated();
const apiClient = integration.mcpServer().session.apiClient;

const projectId = getProjectId();

for (const value of values) {
await session.apiClient.deleteProjectIpAccessList({
await apiClient.deleteProjectIpAccessList({
params: {
path: {
groupId: projectId,
Expand Down
9 changes: 2 additions & 7 deletions tests/integration/tools/atlas/atlasHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { ObjectId } from "mongodb";
import { Group } from "../../../../src/common/atlas/openapi.js";
import { ApiClient } from "../../../../src/common/atlas/apiClient.js";
import { setupIntegrationTest, IntegrationTest } from "../../helpers.js";
import { Session } from "../../../../src/session.js";

export type IntegrationTestFunction = (integration: IntegrationTest) => void;

Expand Down Expand Up @@ -35,19 +34,15 @@ export function withProject(integration: IntegrationTest, fn: ProjectTestFunctio
let projectId: string = "";

beforeAll(async () => {
const session: Session = integration.mcpServer().session;
session.ensureAuthenticated();
const apiClient = integration.mcpServer().session.apiClient;

const apiClient = session.apiClient;
const group = await createProject(apiClient);
projectId = group.id || "";
});

afterAll(async () => {
const session: Session = integration.mcpServer().session;
session.ensureAuthenticated();
const apiClient = integration.mcpServer().session.apiClient;

const apiClient = session.apiClient;
await apiClient.deleteProject({
params: {
path: {
Expand Down
2 changes: 0 additions & 2 deletions tests/integration/tools/atlas/clusters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import { describeAtlas, withProject, sleep, randomId } from "./atlasHelpers.js";
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";

async function deleteAndWaitCluster(session: Session, projectId: string, clusterName: string) {
session.ensureAuthenticated();

await session.apiClient.deleteCluster({
params: {
path: {
Expand Down
1 change: 0 additions & 1 deletion tests/integration/tools/atlas/dbUsers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ describeAtlas("db users", (integration) => {
const projectId = getProjectId();

const session: Session = integration.mcpServer().session;
session.ensureAuthenticated();
await session.apiClient.deleteDatabaseUser({
params: {
path: {
Expand Down
5 changes: 1 addition & 4 deletions tests/integration/tools/atlas/projects.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import { setupIntegrationTest } from "../../helpers.js";
import { Session } from "../../../../src/session.js";
import { ObjectId } from "mongodb";
import { parseTable, describeAtlas } from "./atlasHelpers.js";

Expand All @@ -10,8 +8,7 @@ describeAtlas("projects", (integration) => {
const projName = "testProj-" + randomId;

afterAll(async () => {
const session: Session = integration.mcpServer().session;
session.ensureAuthenticated();
const session = integration.mcpServer().session;

const projects = await session.apiClient.listProjects();
for (const project of projects?.results || []) {
Expand Down
Loading