Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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 and do not set connection string 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.connectionString || !config.apiClientId || !config.apiClientSecret) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

[nit] I find it counter-intuitive that a user needs to unset connectionString, can't it be a connection string to an Atlas cluster?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this requirement is coming from #93

Copy link
Collaborator

Choose a reason for hiding this comment

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

it could be that we thought we needed this before #88? I'll open a thread in slack then we can revert later i guess

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
Loading