Skip to main content
Glama

MongoDB MCP Server

Official
by mongodb-js
config.ts16.9 kB
import argv from "yargs-parser"; import type { CliOptions, ConnectionInfo } from "@mongosh/arg-parser"; import { generateConnectionInfoFromCliArgs } from "@mongosh/arg-parser"; import { Keychain } from "./keychain.js"; import type { Secret } from "./keychain.js"; import { z as z4 } from "zod/v4"; import { commaSeparatedToArray, type ConfigFieldMeta, getExportsPath, getLogPath, isConnectionSpecifier, validateConfigKey, } from "./configUtils.js"; import { OPTIONS } from "./argsParserOptions.js"; import { similarityValues, previewFeatureValues } from "./schemas.js"; export const configRegistry = z4.registry<ConfigFieldMeta>(); export const UserConfigSchema = z4.object({ apiBaseUrl: z4.string().default("https://cloud.mongodb.com/"), apiClientId: z4 .string() .optional() .describe("Atlas API client ID for authentication. Required for running Atlas tools.") .register(configRegistry, { isSecret: true }), apiClientSecret: z4 .string() .optional() .describe("Atlas API client secret for authentication. Required for running Atlas tools.") .register(configRegistry, { isSecret: true }), connectionString: z4 .string() .optional() .describe( "MongoDB connection string for direct database connections. Optional, if not set, you'll need to call the connect tool before interacting with MongoDB data." ) .register(configRegistry, { isSecret: true }), loggers: z4 .preprocess( (val: string | string[] | undefined) => commaSeparatedToArray(val), z4.array(z4.enum(["stderr", "disk", "mcp"])) ) .check( z4.minLength(1, "Cannot be an empty array"), z4.refine((val) => new Set(val).size === val.length, { message: "Duplicate loggers found in config", }) ) .default(["disk", "mcp"]) .describe("An array of logger types.") .register(configRegistry, { defaultValueDescription: '`"disk,mcp"` see below*', }), logPath: z4 .string() .default(getLogPath()) .describe("Folder to store logs.") .register(configRegistry, { defaultValueDescription: "see below*" }), disabledTools: z4 .preprocess((val: string | string[] | undefined) => commaSeparatedToArray(val), z4.array(z4.string())) .default([]) .describe("An array of tool names, operation types, and/or categories of tools that will be disabled."), confirmationRequiredTools: z4 .preprocess((val: string | string[] | undefined) => commaSeparatedToArray(val), z4.array(z4.string())) .default([ "atlas-create-access-list", "atlas-create-db-user", "drop-database", "drop-collection", "delete-many", "drop-index", ]) .describe( "An array of tool names that require user confirmation before execution. Requires the client to support elicitation." ), readOnly: z4 .boolean() .default(false) .describe( "When set to true, only allows read, connect, and metadata operation types, disabling create/update/delete operations." ), indexCheck: z4 .boolean() .default(false) .describe( "When set to true, enforces that query operations must use an index, rejecting queries that perform a collection scan." ), telemetry: z4 .enum(["enabled", "disabled"]) .default("enabled") .describe("When set to disabled, disables telemetry collection."), transport: z4.enum(["stdio", "http"]).default("stdio").describe("Either 'stdio' or 'http'."), httpPort: z4.coerce .number() .int() .min(1, "Invalid httpPort: must be at least 1") .max(65535, "Invalid httpPort: must be at most 65535") .default(3000) .describe("Port number for the HTTP server (only used when transport is 'http')."), httpHost: z4 .string() .default("127.0.0.1") .describe("Host address to bind the HTTP server to (only used when transport is 'http')."), httpHeaders: z4 .object({}) .passthrough() .default({}) .describe( "Header that the HTTP server will validate when making requests (only used when transport is 'http')." ), idleTimeoutMs: z4.coerce .number() .default(600_000) .describe("Idle timeout for a client to disconnect (only applies to http transport)."), notificationTimeoutMs: z4.coerce .number() .default(540_000) .describe("Notification timeout for a client to be aware of disconnect (only applies to http transport)."), maxBytesPerQuery: z4.coerce .number() .default(16_777_216) .describe( "The maximum size in bytes for results from a find or aggregate tool call. This serves as an upper bound for the responseBytesLimit parameter in those tools." ), maxDocumentsPerQuery: z4.coerce .number() .default(100) .describe( "The maximum number of documents that can be returned by a find or aggregate tool call. For the find tool, the effective limit will be the smaller of this value and the tool's limit parameter." ), exportsPath: z4 .string() .default(getExportsPath()) .describe("Folder to store exported data files.") .register(configRegistry, { defaultValueDescription: "see below*" }), exportTimeoutMs: z4.coerce .number() .default(300_000) .describe("Time in milliseconds after which an export is considered expired and eligible for cleanup."), exportCleanupIntervalMs: z4.coerce .number() .default(120_000) .describe("Time in milliseconds between export cleanup cycles that remove expired export files."), atlasTemporaryDatabaseUserLifetimeMs: z4.coerce .number() .default(14_400_000) .describe( "Time in milliseconds that temporary database users created when connecting to MongoDB Atlas clusters will remain active before being automatically deleted." ), voyageApiKey: z4 .string() .default("") .describe( "API key for Voyage AI embeddings service (required for vector search operations with text-to-embedding conversion)." ) .register(configRegistry, { isSecret: true }), disableEmbeddingsValidation: z4 .boolean() .default(false) .describe("When set to true, disables validation of embeddings dimensions."), vectorSearchDimensions: z4.coerce .number() .default(1024) .describe("Default number of dimensions for vector search embeddings."), vectorSearchSimilarityFunction: z4 .enum(similarityValues) .default("euclidean") .describe("Default similarity function for vector search: 'euclidean', 'cosine', or 'dotProduct'."), previewFeatures: z4 .preprocess( (val: string | string[] | undefined) => commaSeparatedToArray(val), z4.array(z4.enum(previewFeatureValues)) ) .default([]) .describe("An array of preview features that are enabled."), }); export type UserConfig = z4.infer<typeof UserConfigSchema> & CliOptions; export const config = setupUserConfig({ cli: process.argv, env: process.env, }); export type DriverOptions = ConnectionInfo["driverOptions"]; export const defaultDriverOptions: DriverOptions = { readConcern: { level: "local", }, readPreference: "secondaryPreferred", writeConcern: { w: "majority", }, timeoutMS: 30_000, proxy: { useEnvironmentVariableProxies: true }, applyProxyToOIDC: true, }; // Gets the config supplied by the user as environment variables. The variable names // are prefixed with `MDB_MCP_` and the keys match the UserConfig keys, but are converted // to SNAKE_UPPER_CASE. function parseEnvConfig(env: Record<string, unknown>): Partial<UserConfig> { const CONFIG_WITH_URLS: Set<string> = new Set<(typeof OPTIONS)["string"][number]>(["connectionString"]); function setValue( obj: Record<string, string | string[] | boolean | number | Record<string, unknown> | undefined>, path: string[], value: string ): void { const currentField = path.shift(); if (!currentField) { return; } if (path.length === 0) { if (CONFIG_WITH_URLS.has(currentField)) { obj[currentField] = value; return; } const numberValue = Number(value); if (!isNaN(numberValue)) { obj[currentField] = numberValue; return; } const booleanValue = value.toLocaleLowerCase(); if (booleanValue === "true" || booleanValue === "false") { obj[currentField] = booleanValue === "true"; return; } // Try to parse an array of values if (value.indexOf(",") !== -1) { obj[currentField] = value.split(",").map((v) => v.trim()); return; } obj[currentField] = value; return; } if (!obj[currentField]) { obj[currentField] = {}; } setValue(obj[currentField] as Record<string, string | string[] | boolean | number | undefined>, path, value); } const result: Record<string, string | string[] | boolean | number | undefined> = {}; const mcpVariables = Object.entries(env).filter( ([key, value]) => value !== undefined && key.startsWith("MDB_MCP_") ) as [string, string][]; for (const [key, value] of mcpVariables) { const fieldPath = key .replace("MDB_MCP_", "") .split(".") .map((part) => SNAKE_CASE_toCamelCase(part)); setValue(result, fieldPath, value); } return result; } function SNAKE_CASE_toCamelCase(str: string): string { return str.toLowerCase().replace(/([-_][a-z])/g, (group) => group.toUpperCase().replace("_", "")); } // Right now we have arguments that are not compatible with the format used in mongosh. // An example is using --connectionString and positional arguments. // We will consolidate them in a way where the mongosh format takes precedence. // We will warn users that previous configuration is deprecated in favour of // whatever is in mongosh. function parseCliConfig(args: string[]): Partial<Record<keyof CliOptions, string | number | undefined>> { const programArgs = args.slice(2); const parsed = argv(programArgs, OPTIONS as unknown as argv.Options) as unknown as Record< keyof CliOptions, string | number | undefined > & { _?: string[]; }; const positionalArguments = parsed._ ?? []; // we use console.warn here because we still don't have our logging system configured // so we don't have a logger. For stdio, the warning will be received as a string in // the client and IDEs like VSCode do show the message in the log window. For HTTP, // it will be in the stdout of the process. warnAboutDeprecatedOrUnknownCliArgs( { ...parsed, _: positionalArguments }, { warn: (msg) => console.warn(msg), exit: (status) => process.exit(status), } ); // if we have a positional argument that matches a connection string // store it as the connection specifier and remove it from the argument // list, so it doesn't get misunderstood by the mongosh args-parser if (!parsed.nodb && isConnectionSpecifier(positionalArguments[0])) { parsed.connectionSpecifier = positionalArguments.shift(); } delete parsed._; return parsed; } export function warnAboutDeprecatedOrUnknownCliArgs( args: Record<string, unknown>, { warn, exit }: { warn: (msg: string) => void; exit: (status: number) => void | never } ): void { let usedDeprecatedArgument = false; let usedInvalidArgument = false; const knownArgs = args as unknown as UserConfig & CliOptions; // the first position argument should be used // instead of --connectionString, as it's how the mongosh works. if (knownArgs.connectionString) { usedDeprecatedArgument = true; warn( "Warning: The --connectionString argument is deprecated. Prefer using the MDB_MCP_CONNECTION_STRING environment variable or the first positional argument for the connection string." ); } for (const providedKey of Object.keys(args)) { if (providedKey === "_") { // positional argument continue; } const { valid, suggestion } = validateConfigKey(providedKey); if (!valid) { usedInvalidArgument = true; if (suggestion) { warn(`Warning: Invalid command line argument '${providedKey}'. Did you mean '${suggestion}'?`); } else { warn(`Warning: Invalid command line argument '${providedKey}'.`); } } } if (usedInvalidArgument || usedDeprecatedArgument) { warn("- Refer to https://www.mongodb.com/docs/mcp-server/get-started/ for setting up the MCP Server."); } if (usedInvalidArgument) { exit(1); } } export function registerKnownSecretsInRootKeychain(userConfig: Partial<UserConfig>): void { const keychain = Keychain.root; const maybeRegister = (value: string | undefined, kind: Secret["kind"]): void => { if (value) { keychain.register(value, kind); } }; maybeRegister(userConfig.apiClientId, "user"); maybeRegister(userConfig.apiClientSecret, "password"); maybeRegister(userConfig.awsAccessKeyId, "password"); maybeRegister(userConfig.awsIamSessionToken, "password"); maybeRegister(userConfig.awsSecretAccessKey, "password"); maybeRegister(userConfig.awsSessionToken, "password"); maybeRegister(userConfig.password, "password"); maybeRegister(userConfig.tlsCAFile, "url"); maybeRegister(userConfig.tlsCRLFile, "url"); maybeRegister(userConfig.tlsCertificateKeyFile, "url"); maybeRegister(userConfig.tlsCertificateKeyFilePassword, "password"); maybeRegister(userConfig.username, "user"); } export function warnIfVectorSearchNotEnabledCorrectly(config: UserConfig, warn: (message: string) => void): void { const vectorSearchEnabled = config.previewFeatures.includes("vectorSearch"); const embeddingsProviderConfigured = !!config.voyageApiKey; if (vectorSearchEnabled && !embeddingsProviderConfigured) { warn(`\ Warning: Vector search is enabled but no embeddings provider is configured. - Set an embeddings provider configuration option to enable auto-embeddings during document insertion and text-based queries with $vectorSearch.\ `); } if (!vectorSearchEnabled && embeddingsProviderConfigured) { warn(`\ Warning: An embeddings provider is configured but the 'vectorSearch' preview feature is not enabled. - Enable vector search by adding 'vectorSearch' to the 'previewFeatures' configuration option, or remove the embeddings provider configuration if not needed.\ `); } } export function setupUserConfig({ cli, env }: { cli: string[]; env: Record<string, unknown> }): UserConfig { const rawConfig = { ...parseEnvConfig(env), ...parseCliConfig(cli), }; if (rawConfig.connectionString && rawConfig.connectionSpecifier) { const connectionInfo = generateConnectionInfoFromCliArgs(rawConfig as UserConfig); rawConfig.connectionString = connectionInfo.connectionString; } const parseResult = UserConfigSchema.safeParse(rawConfig); if (parseResult.error) { throw new Error( `Invalid configuration for the following fields:\n${parseResult.error.issues.map((issue) => `${issue.path.join(".")} - ${issue.message}`).join("\n")}` ); } // We don't have as schema defined for all args-parser arguments so we need to merge the raw config with the parsed config. const userConfig = { ...rawConfig, ...parseResult.data } as UserConfig; warnIfVectorSearchNotEnabledCorrectly(userConfig, (message) => console.warn(message)); registerKnownSecretsInRootKeychain(userConfig); return userConfig; } export function setupDriverConfig({ config, defaults, }: { config: UserConfig; defaults: Partial<DriverOptions>; }): DriverOptions { const { driverOptions } = generateConnectionInfoFromCliArgs(config); return { ...defaults, ...driverOptions, }; }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mongodb-js/mongodb-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server