From 26044c7adff016a6005412b8dfe5d2223cb48e47 Mon Sep 17 00:00:00 2001 From: Filipe C Menezes Date: Wed, 23 Apr 2025 10:13:46 +0100 Subject: [PATCH 1/7] feat: hide atlas tools when not configured --- README.md | 2 ++ src/session.ts | 32 ++++++++++++++-------------- src/tools/atlas/atlasTool.ts | 8 +++++++ src/tools/atlas/createAccessList.ts | 2 -- src/tools/atlas/createDBUser.ts | 2 -- src/tools/atlas/createFreeCluster.ts | 2 -- src/tools/atlas/createProject.ts | 1 - src/tools/atlas/inspectAccessList.ts | 2 -- src/tools/atlas/inspectCluster.ts | 2 -- src/tools/atlas/listClusters.ts | 2 -- src/tools/atlas/listDBUsers.ts | 2 -- src/tools/atlas/listProjects.ts | 2 -- src/tools/tool.ts | 2 +- 13 files changed, 27 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 4043f6e6..9512ef7b 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,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 and do not set connection string on configuration. + #### MongoDB Database Tools - `connect` - Connect to a MongoDB instance diff --git a/src/session.ts b/src/session.ts index 0d5ac951..fcc78a47 100644 --- a/src/session.ts +++ b/src/session.ts @@ -4,24 +4,24 @@ 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." - ); - } - - this.apiClient = new ApiClient({ - baseUrl: config.apiBaseUrl, - credentials: { - clientId: config.apiClientId, - clientSecret: config.apiClientSecret, - }, - }); + constructor() { + let credentials: { + clientId: string; + clientSecret: string; + } | undefined = undefined; + if (config.apiClientId && config.apiClientSecret) { + credentials = { + clientId: config.apiClientId, + clientSecret: config.apiClientSecret, + }; } + + this.apiClient = new ApiClient({ + baseUrl: config.apiBaseUrl, + credentials, + }); } async close(): Promise { diff --git a/src/tools/atlas/atlasTool.ts b/src/tools/atlas/atlasTool.ts index 74683d75..797918c1 100644 --- a/src/tools/atlas/atlasTool.ts +++ b/src/tools/atlas/atlasTool.ts @@ -1,5 +1,6 @@ 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) { @@ -7,4 +8,11 @@ export abstract class AtlasToolBase extends ToolBase { } protected category: ToolCategory = "atlas"; + + protected verifyAllowed(): boolean { + if (config.connectionString || !config.apiClientId || !config.apiClientSecret) { + return false; + } + return super.verifyAllowed(); + } } diff --git a/src/tools/atlas/createAccessList.ts b/src/tools/atlas/createAccessList.ts index eca939d2..46eb9af6 100644 --- a/src/tools/atlas/createAccessList.ts +++ b/src/tools/atlas/createAccessList.ts @@ -27,8 +27,6 @@ export class CreateAccessListTool extends AtlasToolBase { comment, currentIpAddress, }: ToolArgs): Promise { - this.session.ensureAuthenticated(); - if (!ipAddresses?.length && !cidrBlocks?.length && !currentIpAddress) { throw new Error("One of ipAddresses, cidrBlocks, currentIpAddress must be provided."); } diff --git a/src/tools/atlas/createDBUser.ts b/src/tools/atlas/createDBUser.ts index a010c9e2..0b0122c9 100644 --- a/src/tools/atlas/createDBUser.ts +++ b/src/tools/atlas/createDBUser.ts @@ -34,8 +34,6 @@ export class CreateDBUserTool extends AtlasToolBase { roles, clusters, }: ToolArgs): Promise { - this.session.ensureAuthenticated(); - const input = { groupId: projectId, awsIAMType: "NONE", diff --git a/src/tools/atlas/createFreeCluster.ts b/src/tools/atlas/createFreeCluster.ts index 0022bfc4..ccf13856 100644 --- a/src/tools/atlas/createFreeCluster.ts +++ b/src/tools/atlas/createFreeCluster.ts @@ -15,8 +15,6 @@ export class CreateFreeClusterTool extends AtlasToolBase { }; protected async execute({ projectId, name, region }: ToolArgs): Promise { - this.session.ensureAuthenticated(); - const input = { groupId: projectId, name, diff --git a/src/tools/atlas/createProject.ts b/src/tools/atlas/createProject.ts index 26778b06..f99361dd 100644 --- a/src/tools/atlas/createProject.ts +++ b/src/tools/atlas/createProject.ts @@ -14,7 +14,6 @@ export class CreateProjectTool extends AtlasToolBase { }; protected async execute({ projectName, organizationId }: ToolArgs): Promise { - this.session.ensureAuthenticated(); let assumedOrg = false; if (!projectName) { diff --git a/src/tools/atlas/inspectAccessList.ts b/src/tools/atlas/inspectAccessList.ts index 67e0eb40..755da768 100644 --- a/src/tools/atlas/inspectAccessList.ts +++ b/src/tools/atlas/inspectAccessList.ts @@ -12,8 +12,6 @@ export class InspectAccessListTool extends AtlasToolBase { }; protected async execute({ projectId }: ToolArgs): Promise { - this.session.ensureAuthenticated(); - const accessList = await this.session.apiClient.listProjectIpAccessLists({ params: { path: { diff --git a/src/tools/atlas/inspectCluster.ts b/src/tools/atlas/inspectCluster.ts index 447a78f9..c435beaf 100644 --- a/src/tools/atlas/inspectCluster.ts +++ b/src/tools/atlas/inspectCluster.ts @@ -14,8 +14,6 @@ export class InspectClusterTool extends AtlasToolBase { }; protected async execute({ projectId, clusterName }: ToolArgs): Promise { - this.session.ensureAuthenticated(); - const cluster = await this.session.apiClient.getCluster({ params: { path: { diff --git a/src/tools/atlas/listClusters.ts b/src/tools/atlas/listClusters.ts index 6c3355dd..60ce3dbd 100644 --- a/src/tools/atlas/listClusters.ts +++ b/src/tools/atlas/listClusters.ts @@ -13,8 +13,6 @@ export class ListClustersTool extends AtlasToolBase { }; protected async execute({ projectId }: ToolArgs): Promise { - this.session.ensureAuthenticated(); - if (!projectId) { const data = await this.session.apiClient.listClustersForAllProjects(); diff --git a/src/tools/atlas/listDBUsers.ts b/src/tools/atlas/listDBUsers.ts index 52778d9c..c3013162 100644 --- a/src/tools/atlas/listDBUsers.ts +++ b/src/tools/atlas/listDBUsers.ts @@ -13,8 +13,6 @@ export class ListDBUsersTool extends AtlasToolBase { }; protected async execute({ projectId }: ToolArgs): Promise { - this.session.ensureAuthenticated(); - const data = await this.session.apiClient.listDatabaseUsers({ params: { path: { diff --git a/src/tools/atlas/listProjects.ts b/src/tools/atlas/listProjects.ts index adce9b5d..86002d82 100644 --- a/src/tools/atlas/listProjects.ts +++ b/src/tools/atlas/listProjects.ts @@ -9,8 +9,6 @@ export class ListProjectsTool extends AtlasToolBase { protected argsShape = {}; protected async execute(): Promise { - this.session.ensureAuthenticated(); - const data = await this.session.apiClient.listProjects(); if (!data?.results?.length) { diff --git a/src/tools/tool.ts b/src/tools/tool.ts index 0fe6e80f..39056fe7 100644 --- a/src/tools/tool.ts +++ b/src/tools/tool.ts @@ -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}\`,`; From 1befb9346622b7e29dd629884131bb5a57b3de43 Mon Sep 17 00:00:00 2001 From: Filipe C Menezes Date: Wed, 23 Apr 2025 10:17:22 +0100 Subject: [PATCH 2/7] fix: styles --- src/common/atlas/apiClient.ts | 10 ++++++---- src/session.ts | 7 ++----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/common/atlas/apiClient.ts b/src/common/atlas/apiClient.ts index 18f5abda..04e16efa 100644 --- a/src/common/atlas/apiClient.ts +++ b/src/common/atlas/apiClient.ts @@ -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; } diff --git a/src/session.ts b/src/session.ts index fcc78a47..7eaf71b0 100644 --- a/src/session.ts +++ b/src/session.ts @@ -1,5 +1,5 @@ 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 { @@ -7,10 +7,7 @@ export class Session { apiClient: ApiClient; constructor() { - let credentials: { - clientId: string; - clientSecret: string; - } | undefined = undefined; + let credentials: ApiClientCredentials | undefined = undefined; if (config.apiClientId && config.apiClientSecret) { credentials = { clientId: config.apiClientId, From c0b14b1d2ffd230f1f25987d064c56ea1262e880 Mon Sep 17 00:00:00 2001 From: Filipe C Menezes Date: Wed, 23 Apr 2025 10:18:32 +0100 Subject: [PATCH 3/7] fix: styles --- src/session.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/session.ts b/src/session.ts index 7eaf71b0..7e7cb209 100644 --- a/src/session.ts +++ b/src/session.ts @@ -7,13 +7,13 @@ export class Session { apiClient: ApiClient; constructor() { - let credentials: ApiClientCredentials | undefined = undefined; - if (config.apiClientId && config.apiClientSecret) { - credentials = { - clientId: config.apiClientId, - clientSecret: config.apiClientSecret, - }; - } + const credentials: ApiClientCredentials | undefined = + config.apiClientId && config.apiClientSecret + ? { + clientId: config.apiClientId, + clientSecret: config.apiClientSecret, + } + : undefined; this.apiClient = new ApiClient({ baseUrl: config.apiBaseUrl, From d72592488badcf950322ec752eae5d954dceb4c2 Mon Sep 17 00:00:00 2001 From: Filipe C Menezes Date: Wed, 23 Apr 2025 10:43:04 +0100 Subject: [PATCH 4/7] docs: update link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f47dd5b5..e7eeee0c 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ 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 and do not set connection string on configuration. +NOTE: atlas tools are only available when you set credentials and do not set connection string on [configuration](#configuration) section. #### MongoDB Database Tools From a7244616e952782bb38215ffffb061fbdb45d513 Mon Sep 17 00:00:00 2001 From: Filipe C Menezes Date: Wed, 23 Apr 2025 10:45:10 +0100 Subject: [PATCH 5/7] fix: compilation error --- src/tools/atlas/listOrgs.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tools/atlas/listOrgs.ts b/src/tools/atlas/listOrgs.ts index 681298b4..2bfa95c2 100644 --- a/src/tools/atlas/listOrgs.ts +++ b/src/tools/atlas/listOrgs.ts @@ -9,8 +9,6 @@ export class ListOrganizationsTool extends AtlasToolBase { protected argsShape = {}; protected async execute(): Promise { - this.session.ensureAuthenticated(); - const data = await this.session.apiClient.listOrganizations(); if (!data?.results?.length) { From 22d3680ca68e861a58e40b4d46c7c41e9c508b25 Mon Sep 17 00:00:00 2001 From: Filipe C Menezes Date: Wed, 23 Apr 2025 10:51:51 +0100 Subject: [PATCH 6/7] fix: tests --- tests/integration/tools/atlas/accessLists.test.ts | 11 ++++------- tests/integration/tools/atlas/atlasHelpers.ts | 9 ++------- tests/integration/tools/atlas/clusters.test.ts | 2 -- tests/integration/tools/atlas/dbUsers.test.ts | 1 - tests/integration/tools/atlas/projects.test.ts | 5 +---- 5 files changed, 7 insertions(+), 21 deletions(-) diff --git a/tests/integration/tools/atlas/accessLists.test.ts b/tests/integration/tools/atlas/accessLists.test.ts index 9da441e9..a9629961 100644 --- a/tests/integration/tools/atlas/accessLists.test.ts +++ b/tests/integration/tools/atlas/accessLists.test.ts @@ -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() { @@ -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, diff --git a/tests/integration/tools/atlas/atlasHelpers.ts b/tests/integration/tools/atlas/atlasHelpers.ts index c1ed2d68..f7d4802d 100644 --- a/tests/integration/tools/atlas/atlasHelpers.ts +++ b/tests/integration/tools/atlas/atlasHelpers.ts @@ -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; @@ -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: { diff --git a/tests/integration/tools/atlas/clusters.test.ts b/tests/integration/tools/atlas/clusters.test.ts index 83801eb8..bb1b26ee 100644 --- a/tests/integration/tools/atlas/clusters.test.ts +++ b/tests/integration/tools/atlas/clusters.test.ts @@ -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: { diff --git a/tests/integration/tools/atlas/dbUsers.test.ts b/tests/integration/tools/atlas/dbUsers.test.ts index f6d142bd..77104d44 100644 --- a/tests/integration/tools/atlas/dbUsers.test.ts +++ b/tests/integration/tools/atlas/dbUsers.test.ts @@ -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: { diff --git a/tests/integration/tools/atlas/projects.test.ts b/tests/integration/tools/atlas/projects.test.ts index c6833b70..1156468a 100644 --- a/tests/integration/tools/atlas/projects.test.ts +++ b/tests/integration/tools/atlas/projects.test.ts @@ -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"; @@ -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 || []) { From 790b652e55c3e7e45288fe4cf5116840a20e1ccf Mon Sep 17 00:00:00 2001 From: Filipe C Menezes Date: Wed, 23 Apr 2025 12:28:11 +0100 Subject: [PATCH 7/7] fix: ignore connectionString on atlas tools --- README.md | 2 +- src/tools/atlas/atlasTool.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e7eeee0c..09837d07 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ 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 and do not set connection string on [configuration](#configuration) section. +NOTE: atlas tools are only available when you set credentials on [configuration](#configuration) section. #### MongoDB Database Tools diff --git a/src/tools/atlas/atlasTool.ts b/src/tools/atlas/atlasTool.ts index 797918c1..0c2cc0cb 100644 --- a/src/tools/atlas/atlasTool.ts +++ b/src/tools/atlas/atlasTool.ts @@ -10,7 +10,7 @@ export abstract class AtlasToolBase extends ToolBase { protected category: ToolCategory = "atlas"; protected verifyAllowed(): boolean { - if (config.connectionString || !config.apiClientId || !config.apiClientSecret) { + if (!config.apiClientId || !config.apiClientSecret) { return false; } return super.verifyAllowed();