createDBUser.ts•4.67 kB
import { z } from "zod";
import type { ToolArgs, OperationType } from "../../tool.js";
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import { AtlasToolBase } from "../atlasTool.js";
import type { CloudDatabaseUser, DatabaseUserRole } from "../../../common/atlas/openapi.js";
import { generateSecurePassword } from "../../../helpers/generatePassword.js";
import { ensureCurrentIpInAccessList } from "../../../common/atlas/accessListUtils.js";
import { AtlasArgs, CommonArgs } from "../../args.js";
export const CreateDBUserArgs = {
projectId: AtlasArgs.projectId().describe("Atlas project ID"),
username: AtlasArgs.username().describe("Username for the new user"),
// Models will generate overly simplistic passwords like SecurePassword123 or
// AtlasPassword123, which are easily guessable and exploitable. We're instructing
// the model not to try and generate anything and instead leave the field unset.
password: AtlasArgs.password()
.nullish()
.describe(
"Password for the new user. IMPORTANT: If the user hasn't supplied an explicit password, leave it unset and under no circumstances try to generate a random one. A secure password will be generated by the MCP server if necessary."
),
roles: z
.array(
z.object({
roleName: CommonArgs.string().describe("Role name"),
databaseName: CommonArgs.string().describe("Database name").default("admin"),
collectionName: CommonArgs.string().describe("Collection name").optional(),
})
)
.describe("Roles for the new user"),
clusters: z
.array(AtlasArgs.clusterName())
.describe("Clusters to assign the user to, leave empty for access to all clusters")
.optional(),
};
export class CreateDBUserTool extends AtlasToolBase {
public name = "atlas-create-db-user";
protected description = "Create an MongoDB Atlas database user";
public operationType: OperationType = "create";
protected argsShape = {
...CreateDBUserArgs,
};
protected async execute({
projectId,
username,
password,
roles,
clusters,
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
await ensureCurrentIpInAccessList(this.session.apiClient, projectId);
const shouldGeneratePassword = !password;
if (shouldGeneratePassword) {
password = await generateSecurePassword();
}
const input = {
groupId: projectId,
awsIAMType: "NONE",
databaseName: "admin",
ldapAuthType: "NONE",
oidcAuthType: "NONE",
x509Type: "NONE",
username,
password,
roles: roles as unknown as DatabaseUserRole[],
scopes: clusters?.length
? clusters.map((cluster) => ({
type: "CLUSTER",
name: cluster,
}))
: undefined,
} as CloudDatabaseUser;
await this.session.apiClient.createDatabaseUser({
params: {
path: {
groupId: projectId,
},
},
body: input,
});
this.session.keychain.register(username, "user");
if (password) {
this.session.keychain.register(password, "password");
}
return {
content: [
{
type: "text",
text: `User "${username}" created successfully${shouldGeneratePassword ? ` with password: \`${password}\`` : ""}.`,
},
],
};
}
protected getConfirmationMessage({
projectId,
username,
password,
roles,
clusters,
}: ToolArgs<typeof this.argsShape>): string {
return (
`You are about to create a database user in Atlas project \`${projectId}\`:\n\n` +
`**Username**: \`${username}\`\n\n` +
`**Password**: ${password ? "(User-provided password)" : "(Auto-generated secure password)"}\n\n` +
`**Roles**: ${roles.map((role) => `${role.roleName}${role.collectionName ? ` on ${role.databaseName}.${role.collectionName}` : ` on ${role.databaseName}`}`).join(", ")}\n\n` +
`**Cluster Access**: ${clusters?.length ? clusters.join(", ") : "All clusters in the project"}\n\n` +
"This will create a new database user with the specified permissions. " +
"**Do you confirm the execution of the action?**"
);
}
}