Skip to main content
Glama

MongoDB MCP Server

Official
by mongodb-js
generateArguments.ts11.9 kB
#!/usr/bin/env tsx /** * This script generates argument definitions and updates: * - server.json arrays * - README.md configuration table * * It uses the Zod schema and OPTIONS defined in src/common/config.ts */ import { readFileSync, writeFileSync } from "fs"; import { join, dirname } from "path"; import { fileURLToPath } from "url"; import { UserConfigSchema, configRegistry } from "../src/common/config.js"; import assert from "assert"; import { execSync } from "child_process"; import { OPTIONS } from "../src/common/argsParserOptions.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); function camelCaseToSnakeCase(str: string): string { return str.replace(/[A-Z]/g, (letter) => `_${letter}`).toUpperCase(); } // List of mongosh OPTIONS that contain sensitive/secret information // These should be redacted in logs and marked as secret in environment variable definitions const SECRET_OPTIONS_KEYS = new Set([ "connectionString", "username", "password", "tlsCAFile", "tlsCertificateKeyFile", "tlsCertificateKeyFilePassword", "tlsCRLFile", "sslCAFile", "sslPEMKeyFile", "sslPEMKeyPassword", "sslCRLFile", ]); interface ArgumentInfo { name: string; description: string; isRequired: boolean; format: string; isSecret: boolean; configKey: string; defaultValue?: unknown; defaultValueDescription?: string; } interface ConfigMetadata { description: string; defaultValue?: unknown; defaultValueDescription?: string; isSecret?: boolean; } function extractZodDescriptions(): Record<string, ConfigMetadata> { const result: Record<string, ConfigMetadata> = {}; // Get the shape of the Zod schema const shape = UserConfigSchema.shape; for (const [key, fieldSchema] of Object.entries(shape)) { const schema = fieldSchema; // Extract description from Zod schema let description = schema.description || `Configuration option: ${key}`; if ("innerType" in schema.def) { // "pipe" is used for our comma-separated arrays if (schema.def.innerType.def.type === "pipe") { assert( description.startsWith("An array of"), `Field description for field "${key}" with array type does not start with 'An array of'` ); description = description.replace("An array of", "Comma separated values of"); } } // Extract default value if present let defaultValue: unknown = undefined; let defaultValueDescription: string | undefined = undefined; let isSecret: boolean | undefined = undefined; if (schema.def && "defaultValue" in schema.def) { defaultValue = schema.def.defaultValue; } // Get metadata from custom registry const registryMeta = configRegistry.get(schema); if (registryMeta) { defaultValueDescription = registryMeta.defaultValueDescription; isSecret = registryMeta.isSecret; } result[key] = { description, defaultValue, defaultValueDescription, isSecret, }; } return result; } function getArgumentInfo(options: typeof OPTIONS, zodMetadata: Record<string, ConfigMetadata>): ArgumentInfo[] { const argumentInfos: ArgumentInfo[] = []; const processedKeys = new Set<string>(); // Helper to add env var const addEnvVar = (key: string, type: "string" | "number" | "boolean" | "array"): void => { if (processedKeys.has(key)) return; processedKeys.add(key); const envVarName = `MDB_MCP_${camelCaseToSnakeCase(key)}`; // Get description and default value from Zod metadata const metadata = zodMetadata[key] || { description: `Configuration option: ${key}`, }; // Determine format based on type let format = type; if (type === "array") { format = "string"; // Arrays are passed as comma-separated strings } argumentInfos.push({ name: envVarName, description: metadata.description, isRequired: false, format: format, isSecret: metadata.isSecret ?? SECRET_OPTIONS_KEYS.has(key), configKey: key, defaultValue: metadata.defaultValue, defaultValueDescription: metadata.defaultValueDescription, }); }; // Process all string options for (const key of options.string) { addEnvVar(key, "string"); } // Process all number options for (const key of options.number) { addEnvVar(key, "number"); } // Process all boolean options for (const key of options.boolean) { addEnvVar(key, "boolean"); } // Process all array options for (const key of options.array) { addEnvVar(key, "array"); } // Sort by name for consistent output return argumentInfos.sort((a, b) => a.name.localeCompare(b.name)); } function generatePackageArguments(envVars: ArgumentInfo[]): unknown[] { const packageArguments: unknown[] = []; // Generate positional arguments from the same config options (only documented ones) const documentedVars = envVars.filter((v) => !v.description.startsWith("Configuration option:")); // Generate named arguments from the same config options for (const argument of documentedVars) { const arg: Record<string, unknown> = { type: "named", name: "--" + argument.configKey, description: argument.description, isRequired: argument.isRequired, }; // Add format if it's not string (string is the default) if (argument.format !== "string") { arg.format = argument.format; } packageArguments.push(arg); } return packageArguments; } function updateServerJsonEnvVars(envVars: ArgumentInfo[]): void { const serverJsonPath = join(__dirname, "..", "server.json"); const packageJsonPath = join(__dirname, "..", "package.json"); const content = readFileSync(serverJsonPath, "utf-8"); const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")) as { version: string }; const serverJson = JSON.parse(content) as { version?: string; packages: { registryType?: string; identifier: string; environmentVariables: ArgumentInfo[]; packageArguments?: unknown[]; version?: string; }[]; }; // Get version from package.json const version = packageJson.version; // Generate environment variables array (only documented ones) const documentedVars = envVars.filter((v) => !v.description.startsWith("Configuration option:")); const envVarsArray = documentedVars.map((v) => ({ name: v.name, description: v.description, isRequired: v.isRequired, format: v.format, isSecret: v.isSecret, })); // Generate package arguments (named arguments in camelCase) const packageArguments = generatePackageArguments(envVars); // Update version at root level serverJson.version = process.env.VERSION || version; // Update environmentVariables, packageArguments, and version for all packages if (serverJson.packages && Array.isArray(serverJson.packages)) { for (const pkg of serverJson.packages) { pkg.environmentVariables = envVarsArray as ArgumentInfo[]; pkg.packageArguments = packageArguments; // For OCI packages, update the version tag in the identifier and not a version field if (pkg.registryType === "oci") { // Replace the version tag in the OCI identifier (e.g., docker.io/mongodb/mongodb-mcp-server:1.0.0) pkg.identifier = pkg.identifier.replace(/:[^:]+$/, `:${version}`); } else { pkg.version = version; } } } writeFileSync(serverJsonPath, JSON.stringify(serverJson, null, 2) + "\n", "utf-8"); console.log(`✓ Updated server.json (version ${version})`); } function generateReadmeConfigTable(argumentInfos: ArgumentInfo[]): string { const rows = [ "| CLI Option | Environment Variable | Default | Description |", "| -------------------------------------- | --------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |", ]; // Filter to only include options that are in the Zod schema (documented options) const documentedVars = argumentInfos.filter((v) => !v.description.startsWith("Configuration option:")); for (const argumentInfo of documentedVars) { const cliOption = `\`${argumentInfo.configKey}\``; const envVarName = `\`${argumentInfo.name}\``; const defaultValue = argumentInfo.defaultValue; let defaultValueString = argumentInfo.defaultValueDescription ?? "`<not set>`"; if (!argumentInfo.defaultValueDescription && defaultValue !== undefined && defaultValue !== null) { if (Array.isArray(defaultValue)) { defaultValueString = `\`"${defaultValue.join(",")}"\``; } else { // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check switch (typeof defaultValue) { case "number": defaultValueString = `\`${defaultValue}\``; break; case "boolean": defaultValueString = `\`${defaultValue}\``; break; case "string": defaultValueString = `\`"${defaultValue}"\``; break; default: throw new Error(`Unsupported default value type: ${typeof defaultValue}`); } } } const desc = argumentInfo.description.replace(/\|/g, "\\|"); // Escape pipes in description rows.push( `| ${cliOption.padEnd(38)} | ${envVarName.padEnd(51)} | ${defaultValueString.padEnd(75)} | ${desc.padEnd(199)} |` ); } return rows.join("\n"); } function updateReadmeConfigTable(envVars: ArgumentInfo[]): void { const readmePath = join(__dirname, "..", "README.md"); let content = readFileSync(readmePath, "utf-8"); const newTable = generateReadmeConfigTable(envVars); // Find and replace the configuration options table const tableRegex = /### Configuration Options\n\n\| CLI Option[\s\S]*?\n\n####/; const replacement = `### Configuration Options\n\n${newTable}\n\n####`; content = content.replace(tableRegex, replacement); writeFileSync(readmePath, content, "utf-8"); console.log("✓ Updated README.md configuration table"); // Run prettier on the README.md file execSync("npx prettier --write README.md", { cwd: join(__dirname, "..") }); } function main(): void { const zodMetadata = extractZodDescriptions(); const argumentInfo = getArgumentInfo(OPTIONS, zodMetadata); updateServerJsonEnvVars(argumentInfo); updateReadmeConfigTable(argumentInfo); } main();

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