MongoDB Atlas MCP Server
Official
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import crypto from 'crypto';
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
} from '@modelcontextprotocol/sdk/types.js';
interface CreateClusterInput {
projectId: string;
clusterName: string;
region: string;
cloudProvider: string;
tier: string;
}
interface NetworkAccessInput {
projectId: string;
ipAddresses: string[];
}
interface CreateUserInput {
projectId: string;
username: string;
password: string;
roles: string[];
}
interface ConnectionStringsInput {
projectId: string;
clusterName: string;
}
interface ListClustersInput {
projectId: string;
}
const isValidCreateClusterInput = (args: any): args is CreateClusterInput =>
typeof args === 'object' &&
args !== null &&
typeof args.projectId === 'string' &&
typeof args.clusterName === 'string' &&
typeof args.region === 'string' &&
typeof args.cloudProvider === 'string' &&
typeof args.tier === 'string';
class AtlasProjectManager {
private server: Server;
private apiKey: string;
private privateKey: string;
private async makeAtlasRequest(url: string, method: string, body?: any) {
// Step 1: Make initial request to get digest challenge
const initialResponse = await fetch(url, {
method,
headers: {
'Content-Type': 'application/json'
},
body: body ? JSON.stringify(body) : undefined
});
// Check if we got a 401 with WWW-Authenticate header (digest challenge)
if (initialResponse.status === 401) {
const wwwAuthHeader = initialResponse.headers.get('WWW-Authenticate');
if (!wwwAuthHeader || !wwwAuthHeader.startsWith('Digest ')) {
throw new Error('Expected Digest authentication challenge not received');
}
// Parse the digest challenge
const authDetails: Record<string, string> = {};
wwwAuthHeader.substring(7).split(',').forEach(part => {
const [key, value] = part.trim().split('=');
// Remove quotes if present
authDetails[key] = value.startsWith('"') ? value.slice(1, -1) : value;
});
// Generate a random client nonce (cnonce)
const cnonce = Math.random().toString(36).substring(2, 15);
const nc = '00000001'; // nonce count, incremented for each request with the same nonce
// Calculate the response hash
const ha1 = this.md5(`${this.apiKey}:${authDetails.realm}:${this.privateKey}`);
const ha2 = this.md5(`${method}:${new URL(url).pathname}`);
const response = this.md5(`${ha1}:${authDetails.nonce}:${nc}:${cnonce}:${authDetails.qop}:${ha2}`);
// Build the Authorization header
const authHeader = `Digest username="${this.apiKey}", realm="${authDetails.realm}", nonce="${authDetails.nonce}", uri="${new URL(url).pathname}", qop=${authDetails.qop}, nc=${nc}, cnonce="${cnonce}", response="${response}", algorithm=${authDetails.algorithm || 'MD5'}`;
// Make the actual request with the digest authentication
const digestResponse = await fetch(url, {
method,
headers: {
'Content-Type': 'application/json',
'Authorization': authHeader
},
body: body ? JSON.stringify(body) : undefined
});
if (!digestResponse.ok) {
throw new Error(`Atlas API error: ${digestResponse.statusText}`);
}
return digestResponse.json();
} else if (initialResponse.ok) {
// If the initial request succeeded without authentication (unlikely)
return initialResponse.json();
} else {
throw new Error(`Atlas API error: ${initialResponse.statusText}`);
}
}
// Helper method to calculate MD5 hash
private md5(data: string): string {
// Use createRequire to enable require in ES modules
return crypto.createHash('md5').update(data).digest('hex');
}
private async createAtlasCluster(input: CreateClusterInput) {
if (input.tier === 'M0') {
return {
content: [{
type: 'text',
text: 'M0 (Free Tier) clusters cannot be created via the API. Please use the MongoDB Atlas UI to create an M0 cluster.'
}],
isError: true
};
}
try {
const url = `https://cloud.mongodb.com/api/atlas/v1.0/groups/${input.projectId}/clusters?pretty=true`;
const body = {
name: input.clusterName,
providerSettings: {
providerName: input.cloudProvider,
instanceSizeName: input.tier,
regionName: input.region
}
};
const result = await this.makeAtlasRequest(url, 'POST', body);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
} catch (error: any) {
return {
content: [{
type: 'text',
text: error.message
}],
isError: true
};
}
}
private async setupAtlasNetworkAccess(input: NetworkAccessInput) {
try {
const url = `https://cloud.mongodb.com/api/atlas/v1.0/groups/${input.projectId}/accessList`;
const body = input.ipAddresses.map(ip => ({
ipAddress: ip,
comment: "Added via Atlas Project Manager MCP"
}));
const result = await this.makeAtlasRequest(url, 'POST', body);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
} catch (error: any) {
return {
content: [{
type: 'text',
text: error.message
}],
isError: true
};
}
}
private async createAtlasUser(input: CreateUserInput) {
try {
const url = `https://cloud.mongodb.com/api/atlas/v1.0/groups/${input.projectId}/databaseUsers`;
const body = {
databaseName: "admin",
username: input.username,
password: input.password,
roles: input.roles.map(role => ({ databaseName: 'admin', roleName: role }))
};
const result = await this.makeAtlasRequest(url, 'POST', body);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
} catch (error: any) {
return {
content: [{
type: 'text',
text: error.message
}],
isError: true
};
}
}
private async getAtlasConnectionStrings(input: ConnectionStringsInput) {
try {
const url = `https://cloud.mongodb.com/api/atlas/v1.0/groups/${input.projectId}/clusters/${input.clusterName}`;
const result = await this.makeAtlasRequest(url, 'GET');
// Add appName to connection strings if they exist
if (result.connectionStrings) {
this.addAppNameToConnectionStrings(result);
}
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
} catch (error: any) {
return {
content: [{
type: 'text',
text: error.message
}],
isError: true
};
}
}
private async listAtlasProjects() {
try {
const url = 'https://cloud.mongodb.com/api/atlas/v1.0/groups';
const result = await this.makeAtlasRequest(url, 'GET');
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
} catch (error: any) {
return {
content: [{
type: 'text',
text: error.message
}],
isError: true
};
}
}
// Helper method to add appName to connection strings
private addAppNameToConnectionStrings(result: any) {
const appName = "devrel.integration.mcp-atlas";
// Helper function to safely add appName parameter to a connection string
const addAppNameParam = (connectionString: string): string => {
if (!connectionString) return connectionString;
// Add appName parameter
return connectionString + (connectionString.includes('?') ? '&' : '?') + `appName=${appName}`;
};
// Handle single cluster object
if (result.connectionStrings) {
// Add appName to standard connection string
if (result.connectionStrings.standard) {
result.connectionStrings.standard = addAppNameParam(result.connectionStrings.standard);
}
// Add appName to standardSrv connection string
if (result.connectionStrings.standardSrv) {
result.connectionStrings.standardSrv = addAppNameParam(result.connectionStrings.standardSrv);
}
// Add appName to other connection string formats
if (result.mongoURI) {
result.mongoURI = addAppNameParam(result.mongoURI);
}
if (result.mongoURIWithOptions) {
result.mongoURIWithOptions = addAppNameParam(result.mongoURIWithOptions);
}
if (result.srvAddress) {
result.srvAddress = addAppNameParam(result.srvAddress);
}
}
// Handle array of clusters (for listAtlasClusters)
if (result.results && Array.isArray(result.results)) {
result.results.forEach((cluster: any) => {
if (cluster.connectionStrings) {
this.addAppNameToConnectionStrings(cluster);
}
});
}
return result;
}
private async listAtlasClusters(input: ListClustersInput) {
try {
const url = `https://cloud.mongodb.com/api/atlas/v1.0/groups/${input.projectId}/clusters`;
const result = await this.makeAtlasRequest(url, 'GET');
// Add appName to connection strings in all clusters
this.addAppNameToConnectionStrings(result);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
} catch (error: any) {
return {
content: [{
type: 'text',
text: error.message
}],
isError: true
};
}
}
constructor() {
this.server = new Server(
{
name: 'atlas-project-manager',
version: '0.1.0',
},
{
capabilities: {
tools: {},
},
}
);
let apiKey = process.env.ATLAS_PUBLIC_KEY;
let privateKey = process.env.ATLAS_PRIVATE_KEY;
if (!apiKey || !privateKey) {
const args = process.argv.slice(2);
if (args.length >= 2) {
apiKey = args[0];
privateKey = args[1];
console.error('Using API keys from command line arguments');
} else {
throw new Error('ATLAS_PUBLIC_KEY and ATLAS_PRIVATE_KEY must be provided either as environment variables or as command line arguments');
}
}
this.apiKey = apiKey;
this.privateKey = privateKey;
this.setupToolHandlers();
this.server.onerror = (error) => console.error('[MCP Error]', error);
process.on('SIGINT', async () => {
await this.server.close();
process.exit(0);
});
}
private setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'create_atlas_cluster',
description: 'Creates a new Atlas cluster in an existing Atlas project.',
inputSchema: {
type: 'object',
properties: {
projectId: {
type: 'string',
description: 'The ID of the Atlas project.',
},
clusterName: {
type: 'string',
description: 'The name of the cluster to create.',
},
region: {
type: 'string',
description: 'The cloud provider region to deploy the cluster in. eg. US_EAST_1',
},
cloudProvider: {
type: 'string',
description: 'The cloud provider (e.g., AWS, GCP, AZURE).',
},
tier: {
type: 'string',
description: 'The instance size (e.g., M0, M2, M5).',
}
},
required: ['projectId', 'clusterName', 'region', 'cloudProvider', 'tier'],
},
},
{
name: 'setup_atlas_network_access',
description: 'Sets up network access for an existing Atlas project. Accepts list of IP addresses or CIDR blocks.',
inputSchema: {
type: 'object',
properties: {
projectId: {
type: 'string',
description: 'The ID of the Atlas project.',
},
ipAddresses: {
type: 'array',
items: {
type: 'string',
},
description: 'An array of IP addresses or CIDR blocks for network access.',
},
},
required: ['projectId', 'ipAddresses'],
},
},
{
name: 'create_atlas_user',
description: 'Creates a new database user for an existing Atlas project. User will have atlasAdmin role.',
inputSchema: {
type: 'object',
properties: {
projectId: {
type: 'string',
description: 'The ID of the Atlas project.',
},
username: {
type: 'string',
description: 'The username for the database user.',
},
password: {
type: 'string',
description: 'The password for the database user.',
},
roles: {
type: 'array',
items: {
type: 'string',
},
description: 'An array of roles for the user. Default is [atlasAdmin].',
}
},
required: ['projectId', 'username', 'password'],
},
},
{
name: 'get_atlas_connection_strings',
description: 'Retrieves connection strings for a cluster in an existing Atlas project.',
inputSchema: {
type: 'object',
properties: {
projectId: {
type: 'string',
description: 'The ID of the Atlas project.',
},
clusterName: {
type: 'string',
description: 'The name of the cluster.',
},
},
required: ['projectId', 'clusterName'],
},
},
{
name: 'list_atlas_projects',
description: 'Lists all Atlas projects that the API key has access to.',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'list_atlas_clusters',
description: 'Lists all clusters in an Atlas project.',
inputSchema: {
type: 'object',
properties: {
projectId: {
type: 'string',
description: 'The ID of the Atlas project.',
},
},
required: ['projectId'],
},
}
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
if (!['create_atlas_cluster', 'setup_atlas_network_access', 'create_atlas_user', 'get_atlas_connection_strings', 'list_atlas_projects', 'list_atlas_clusters'].includes(request.params.name)) {
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
);
}
if (!request.params.arguments) {
throw new McpError(ErrorCode.InvalidParams, 'Missing arguments');
}
const input = request.params.arguments as Record<string, unknown>;
switch (request.params.name) {
case 'create_atlas_cluster':
if (!isValidCreateClusterInput(input)) {
throw new McpError(ErrorCode.InvalidParams, 'Invalid cluster creation arguments');
}
break;
case 'setup_atlas_network_access':
if (!input.projectId || !input.ipAddresses || !Array.isArray(input.ipAddresses)) {
throw new McpError(ErrorCode.InvalidParams, 'Invalid network access arguments');
}
break;
case 'create_atlas_user':
if (!input.projectId || !input.username || !input.password) {
throw new McpError(ErrorCode.InvalidParams, 'Invalid user creation arguments');
}
break;
case 'get_atlas_connection_strings':
if (!input.projectId || !input.clusterName) {
throw new McpError(ErrorCode.InvalidParams, 'Invalid connection string arguments');
}
break;
case 'list_atlas_clusters':
if (!input.projectId) {
throw new McpError(ErrorCode.InvalidParams, 'Invalid list clusters arguments');
}
break;
}
let result;
try {
switch (request.params.name) {
case 'create_atlas_cluster':
result = await this.createAtlasCluster(input as unknown as CreateClusterInput);
break;
case 'setup_atlas_network_access':
result = await this.setupAtlasNetworkAccess(input as unknown as NetworkAccessInput);
break;
case 'create_atlas_user':
result = await this.createAtlasUser(input as unknown as CreateUserInput);
break;
case 'get_atlas_connection_strings':
result = await this.getAtlasConnectionStrings(input as unknown as ConnectionStringsInput);
break;
case 'list_atlas_projects':
result = await this.listAtlasProjects();
break;
case 'list_atlas_clusters':
result = await this.listAtlasClusters(input as unknown as ListClustersInput);
break;
default:
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
}
// Ensure we return the expected format
return {
content: result.content,
_meta: request.params._meta
};
} catch (error: any) {
// Handle any errors that might occur
return {
content: [{
type: 'text',
text: `Error: ${error.message}`
}],
isError: true,
_meta: request.params._meta
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Atlas Project Manager MCP server running on stdio');
}
}
const server = new AtlasProjectManager();
server.run().catch(console.error);