From 4b8fe455d452e43d29a932a26c6c8efa8500d658 Mon Sep 17 00:00:00 2001 From: gagik Date: Sun, 14 Dec 2025 21:32:40 +0100 Subject: [PATCH 1/8] chore: support schema overrides and parserOptions --- MCP_SERVER_LIBRARY.md | 11 ++-- src/common/config/createUserConfig.ts | 84 ++++++++++++++++++--------- src/lib.ts | 2 +- src/transports/base.ts | 2 +- tests/unit/common/config.test.ts | 34 ++++++++++- 5 files changed, 96 insertions(+), 37 deletions(-) diff --git a/MCP_SERVER_LIBRARY.md b/MCP_SERVER_LIBRARY.md index dd0da22be..3b7746714 100644 --- a/MCP_SERVER_LIBRARY.md +++ b/MCP_SERVER_LIBRARY.md @@ -49,7 +49,7 @@ import { Session, UserConfig, UserConfigSchema, - parseCliArgumentsAsUserConfig, + parseUserConfig, StreamableHttpRunner, StdioRunner, TransportRunnerBase, @@ -811,26 +811,25 @@ const customConfig = UserConfigSchema.parse({ This approach ensures you get all the default values without having to specify every configuration key manually. -### parseCliArgumentsAsUserConfig +### parseUserConfig Utility function to parse command-line arguments and environment variables into a UserConfig object, using the same parsing logic as the MongoDB MCP server CLI. _Note: This is what MongoDB MCP server uses internally._ ```typescript -function parseCliArgumentsAsUserConfig(options?: { +function parseUserConfig(options?: { args?: string[]; - helpers?: CreateUserConfigHelpers; }): UserConfig; ``` **Example:** ```typescript -import { parseCliArgumentsAsUserConfig, StdioRunner } from "mongodb-mcp-server"; +import { parseUserConfig, StdioRunner } from "mongodb-mcp-server"; // Parse config from process.argv and environment variables -const config = parseCliArgumentsAsUserConfig(); +const config = parseUserConfig(); const runner = new StdioRunner({ userConfig: config }); await runner.start(); diff --git a/src/common/config/createUserConfig.ts b/src/common/config/createUserConfig.ts index b04739840..859f43000 100644 --- a/src/common/config/createUserConfig.ts +++ b/src/common/config/createUserConfig.ts @@ -4,19 +4,52 @@ import type { Secret } from "../keychain.js"; import { matchingConfigKey } from "./configUtils.js"; import { UserConfigSchema, type UserConfig } from "./userConfig.js"; import { - defaultParserOptions, + defaultParserOptions as defaultArgParserOptions, parseArgsWithCliOptions, CliOptionsSchema, UnknownArgumentError, } from "@mongosh/arg-parser/arg-parser"; -import type { z as z4 } from "zod/v4"; - -export function createUserConfig({ args }: { args: string[] }): { +import { z as z4 } from "zod/v4"; +import type yargsParser from "yargs-parser"; + +export type ParserOptions = Partial; + +export const defaultParserOptions = { + // This is the name of key that yargs-parser will look up in CLI + // arguments (--config) and ENV variables (MDB_MCP_CONFIG) to load an + // initial configuration from. + config: "config", + // This helps parse the relevant environment variables. + envPrefix: "MDB_MCP_", + configuration: { + ...defaultArgParserOptions.configuration, + // To avoid populating `_` with end-of-flag arguments we explicitly + // populate `--` variable and altogether ignore them later. + "populate--": true, + }, +} satisfies ParserOptions; + +export function createUserConfig({ + args, + overrides, + parserOptions = defaultParserOptions, +}: { + args: string[]; + overrides?: z4.ZodRawShape; + parserOptions?: ParserOptions; +}): { warnings: string[]; parsed: UserConfig | undefined; error: string | undefined; } { - const { error: parseError, warnings, parsed } = parseUserConfigSources(args); + const schema = overrides + ? z4.object({ + ...UserConfigSchema.shape, + ...overrides, + }) + : UserConfigSchema; + + const { error: parseError, warnings, parsed } = parseUserConfigSources({ args, schema, parserOptions }); if (parseError) { return { error: parseError, warnings, parsed: undefined }; @@ -40,7 +73,7 @@ export function createUserConfig({ args }: { args: string[] }): { parsed.connectionString = connectionInfo.connectionString; } - const configParseResult = UserConfigSchema.safeParse(parsed); + const configParseResult = schema.safeParse(parsed); const mongoshArguments = CliOptionsSchema.safeParse(parsed); const error = configParseResult.error || mongoshArguments.error; if (error) { @@ -62,34 +95,29 @@ export function createUserConfig({ args }: { args: string[] }): { }; } -function parseUserConfigSources(cliArguments: string[]): { +function parseUserConfigSources({ + args, + schema = UserConfigSchema as T, + parserOptions, +}: { + args: string[]; + schema: T; + parserOptions: ParserOptions; +}): { error: string | undefined; warnings: string[]; - parsed: Partial>; + parsed: Partial>; } { - let parsed: Partial>; - let deprecated: Record; + let parsed: Partial>; + let deprecated: Record; try { const { parsed: parsedResult, deprecated: deprecatedResult } = parseArgsWithCliOptions({ - args: cliArguments, - schema: UserConfigSchema, - parserOptions: { - // This is the name of key that yargs-parser will look up in CLI - // arguments (--config) and ENV variables (MDB_MCP_CONFIG) to load an - // initial configuration from. - config: "config", - // This helps parse the relevant environment variables. - envPrefix: "MDB_MCP_", - configuration: { - ...defaultParserOptions.configuration, - // To avoid populating `_` with end-of-flag arguments we explicitly - // populate `--` variable and altogether ignore them later. - "populate--": true, - }, - }, + args, + schema, + parserOptions, }); parsed = parsedResult; - deprecated = deprecatedResult; + deprecated = deprecatedResult as Record; // Delete fileNames - this is a field populated by mongosh but not used by us. delete parsed.fileNames; @@ -112,7 +140,7 @@ function parseUserConfigSources(cliArguments: string[]): { } const deprecationWarnings = [ - ...getWarnings(parsed, cliArguments), + ...getWarnings(parsed, args), ...Object.entries(deprecated).map(([deprecated, replacement]) => { return `Warning: The --${deprecated} argument is deprecated. Use --${replacement} instead.`; }), diff --git a/src/lib.ts b/src/lib.ts index 3b05eb54f..b06c36980 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -1,7 +1,7 @@ export { Server, type ServerOptions } from "./server.js"; export { Session, type SessionOptions } from "./common/session.js"; export { type UserConfig, UserConfigSchema } from "./common/config/userConfig.js"; -export { createUserConfig as parseCliArgumentsAsUserConfig } from "./common/config/createUserConfig.js"; +export { createUserConfig, defaultParserOptions, type ParserOptions } from "./common/config/createUserConfig.js"; export { LoggerBase, type LogPayload, type LoggerType, type LogLevel } from "./common/logger.js"; export { StreamableHttpRunner } from "./transports/streamableHttp.js"; export { StdioRunner } from "./transports/stdio.js"; diff --git a/src/transports/base.ts b/src/transports/base.ts index 3e6a661b5..4e0731df1 100644 --- a/src/transports/base.ts +++ b/src/transports/base.ts @@ -75,7 +75,7 @@ export type TransportRunnerConfig = { * * To parse CLI arguments and environment variables in order to generate a * `UserConfig` object, you can use `createUserConfig` function, also - * exported as `parseCliArgumentsAsUserConfig` through MCP server library + * exported as `parseUserConfig` through MCP server library * exports. * * Optionally, you can also use `UserConfigSchema` (available through MCP diff --git a/tests/unit/common/config.test.ts b/tests/unit/common/config.test.ts index 5e4806458..9e25debb5 100644 --- a/tests/unit/common/config.test.ts +++ b/tests/unit/common/config.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest"; import { type UserConfig, UserConfigSchema } from "../../../src/common/config/userConfig.js"; -import { createUserConfig } from "../../../src/common/config/createUserConfig.js"; +import { createUserConfig, defaultParserOptions } from "../../../src/common/config/createUserConfig.js"; import { getLogPath, getExportsPath, @@ -69,6 +69,24 @@ describe("config", () => { }); }); + it("can override defaults in the schema and those are populated instead", () => { + expect( + createUserConfig({ + args: [], + overrides: { + exportTimeoutMs: UserConfigSchema.shape.exportTimeoutMs.default(123), + }, + }) + ).toStrictEqual({ + parsed: { + ...expectedDefaults, + exportTimeoutMs: 123, + }, + warnings: [], + error: undefined, + }); + }); + describe("env var parsing", () => { const { setVariable, clearVariables } = createEnvironment(); @@ -143,6 +161,20 @@ describe("config", () => { }); } }); + + it("works with custom prefixes through parserOptions", () => { + setVariable("CUSTOM_MCP_DISABLED_TOOLS", "find,export"); + // Ensure our own ENV doesn't affect it + setVariable("MDB_MCP_DISABLED_TOOLS", "explain"); + const { parsed: actual } = createUserConfig({ + args: [], + parserOptions: { + ...defaultParserOptions, + envPrefix: "CUSTOM_MCP_", + }, + }); + expect(actual?.disabledTools).toEqual(["find", "export"]); + }); }); describe("cli parsing", () => { From 5ad2fa4efcc74374498643516c84112e05e0170d Mon Sep 17 00:00:00 2001 From: gagik Date: Sun, 14 Dec 2025 21:33:06 +0100 Subject: [PATCH 2/8] createUserConfig => parseUserConfig --- eslint-rules/enforce-zod-v4.js | 2 +- src/common/config/createUserConfig.ts | 4 +- src/index.ts | 4 +- src/lib.ts | 2 +- src/transports/base.ts | 2 +- tests/unit/common/config.test.ts | 84 +++++++++++++-------------- 6 files changed, 49 insertions(+), 49 deletions(-) diff --git a/eslint-rules/enforce-zod-v4.js b/eslint-rules/enforce-zod-v4.js index c0462cf10..f2b05748f 100644 --- a/eslint-rules/enforce-zod-v4.js +++ b/eslint-rules/enforce-zod-v4.js @@ -4,7 +4,7 @@ import path from "path"; // The file that is allowed to import from zod/v4 const allowedFilePaths = [ path.resolve(import.meta.dirname, "../src/common/config/userConfig.ts"), - path.resolve(import.meta.dirname, "../src/common/config/createUserConfig.ts"), + path.resolve(import.meta.dirname, "../src/common/config/parseUserConfig.ts"), ]; // Ref: https://eslint.org/docs/latest/extend/custom-rules diff --git a/src/common/config/createUserConfig.ts b/src/common/config/createUserConfig.ts index 859f43000..3d3403700 100644 --- a/src/common/config/createUserConfig.ts +++ b/src/common/config/createUserConfig.ts @@ -29,14 +29,14 @@ export const defaultParserOptions = { }, } satisfies ParserOptions; -export function createUserConfig({ +export function parseUserConfig({ args, overrides, parserOptions = defaultParserOptions, }: { args: string[]; overrides?: z4.ZodRawShape; - parserOptions?: ParserOptions; + parserOptions: ParserOptions; }): { warnings: string[]; parsed: UserConfig | undefined; diff --git a/src/index.ts b/src/index.ts index c1f7fc531..ce8b2b7de 100644 --- a/src/index.ts +++ b/src/index.ts @@ -37,7 +37,7 @@ enableFipsIfRequested(); import crypto from "crypto"; import { ConsoleLogger, LogId } from "./common/logger.js"; -import { createUserConfig } from "./common/config/createUserConfig.js"; +import { parseUserConfig } from "./common/config/parseUserConfig.js"; import { type UserConfig } from "./common/config/userConfig.js"; import { packageInfo } from "./common/packageInfo.js"; import { StdioRunner } from "./transports/stdio.js"; @@ -53,7 +53,7 @@ async function main(): Promise { error, warnings, parsed: config, - } = createUserConfig({ + } = parseUserConfig({ args: process.argv.slice(2), }); diff --git a/src/lib.ts b/src/lib.ts index b06c36980..54834addb 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -1,7 +1,7 @@ export { Server, type ServerOptions } from "./server.js"; export { Session, type SessionOptions } from "./common/session.js"; export { type UserConfig, UserConfigSchema } from "./common/config/userConfig.js"; -export { createUserConfig, defaultParserOptions, type ParserOptions } from "./common/config/createUserConfig.js"; +export { parseUserConfig, defaultParserOptions, type ParserOptions } from "./common/config/parseUserConfig.js"; export { LoggerBase, type LogPayload, type LoggerType, type LogLevel } from "./common/logger.js"; export { StreamableHttpRunner } from "./transports/streamableHttp.js"; export { StdioRunner } from "./transports/stdio.js"; diff --git a/src/transports/base.ts b/src/transports/base.ts index 4e0731df1..58a618aed 100644 --- a/src/transports/base.ts +++ b/src/transports/base.ts @@ -74,7 +74,7 @@ export type TransportRunnerConfig = { * `UserConfig`. * * To parse CLI arguments and environment variables in order to generate a - * `UserConfig` object, you can use `createUserConfig` function, also + * `UserConfig` object, you can use `parseUserConfig` function, also * exported as `parseUserConfig` through MCP server library * exports. * diff --git a/tests/unit/common/config.test.ts b/tests/unit/common/config.test.ts index 9e25debb5..ae66c1002 100644 --- a/tests/unit/common/config.test.ts +++ b/tests/unit/common/config.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest"; import { type UserConfig, UserConfigSchema } from "../../../src/common/config/userConfig.js"; -import { createUserConfig, defaultParserOptions } from "../../../src/common/config/createUserConfig.js"; +import { parseUserConfig, defaultParserOptions } from "../../../src/common/config/parseUserConfig.js"; import { getLogPath, getExportsPath, @@ -62,7 +62,7 @@ describe("config", () => { }); it("should generate defaults when no config sources are populated", () => { - expect(createUserConfig({ args: [] })).toStrictEqual({ + expect(parseUserConfig({ args: [] })).toStrictEqual({ parsed: expectedDefaults, warnings: [], error: undefined, @@ -71,7 +71,7 @@ describe("config", () => { it("can override defaults in the schema and those are populated instead", () => { expect( - createUserConfig({ + parseUserConfig({ args: [], overrides: { exportTimeoutMs: UserConfigSchema.shape.exportTimeoutMs.default(123), @@ -97,7 +97,7 @@ describe("config", () => { describe("mongodb urls", () => { it("should not try to parse a multiple-host urls", () => { setVariable("MDB_MCP_CONNECTION_STRING", "mongodb://user:password@host1,host2,host3/"); - const { parsed: actual } = createUserConfig({ args: [] }); + const { parsed: actual } = parseUserConfig({ args: [] }); expect(actual?.connectionString).toEqual("mongodb://user:password@host1,host2,host3/"); }); }); @@ -141,7 +141,7 @@ describe("config", () => { for (const { envVar, property, value, expectedValue } of testCases) { it(`should map ${envVar} to ${property} with value "${String(value)}" to "${String(expectedValue ?? value)}"`, () => { setVariable(envVar, value); - const { parsed: actual } = createUserConfig({ args: [] }); + const { parsed: actual } = parseUserConfig({ args: [] }); expect(actual?.[property]).toBe(expectedValue ?? value); }); } @@ -156,7 +156,7 @@ describe("config", () => { for (const { envVar, property, value } of testCases) { it(`should map ${envVar} to ${property}`, () => { setVariable(envVar, value); - const { parsed: actual } = createUserConfig({ args: [] }); + const { parsed: actual } = parseUserConfig({ args: [] }); expect(actual?.[property]).toEqual(value.split(",")); }); } @@ -166,7 +166,7 @@ describe("config", () => { setVariable("CUSTOM_MCP_DISABLED_TOOLS", "find,export"); // Ensure our own ENV doesn't affect it setVariable("MDB_MCP_DISABLED_TOOLS", "explain"); - const { parsed: actual } = createUserConfig({ + const { parsed: actual } = parseUserConfig({ args: [], parserOptions: { ...defaultParserOptions, @@ -179,7 +179,7 @@ describe("config", () => { describe("cli parsing", () => { it("should not try to parse a multiple-host urls", () => { - const { parsed: actual } = createUserConfig({ + const { parsed: actual } = parseUserConfig({ args: ["--connectionString", "mongodb://user:password@host1,host2,host3/"], }); @@ -189,7 +189,7 @@ describe("config", () => { it("positional connection specifier gets accounted for even without other connection sources", () => { // Note that neither connectionString argument nor env variable is // provided. - const { parsed: actual } = createUserConfig({ + const { parsed: actual } = parseUserConfig({ args: ["mongodb://host1:27017"], }); expect(actual?.connectionString).toEqual("mongodb://host1:27017/?directConnection=true"); @@ -365,7 +365,7 @@ describe("config", () => { for (const { cli, expected } of testCases) { it(`should parse '${cli.join(" ")}' to ${JSON.stringify(expected)}`, () => { - const { parsed, error } = createUserConfig({ + const { parsed, error } = parseUserConfig({ args: cli, }); expect(error).toBeUndefined(); @@ -390,7 +390,7 @@ describe("config", () => { ] as { cli: string[]; expected: Partial }[]; for (const { cli, expected } of testCases) { it(`should parse '${cli.join(" ")}' to ${JSON.stringify(expected)}`, () => { - const { parsed } = createUserConfig({ + const { parsed } = parseUserConfig({ args: cli, }); expect(parsed?.httpHeaders).toStrictEqual(expected.httpHeaders); @@ -399,7 +399,7 @@ describe("config", () => { it("cannot mix --httpHeaders and --httpHeaders.fieldX", () => { expect( - createUserConfig({ + parseUserConfig({ args: ["--httpHeaders", '{"fieldA": "3", "fieldB": "4"}', "--httpHeaders.fieldA", "5"], }) ).toStrictEqual({ @@ -531,7 +531,7 @@ describe("config", () => { for (const { cli, expected } of testCases) { it(`should parse '${cli.join(" ")}' to ${JSON.stringify(expected)}`, () => { - const { parsed: actual } = createUserConfig({ + const { parsed: actual } = parseUserConfig({ args: cli, }); for (const [key, value] of Object.entries(expected)) { @@ -555,7 +555,7 @@ describe("config", () => { for (const { cli, expected } of testCases) { it(`should parse '${cli.join(" ")}' to ${JSON.stringify(expected)}`, () => { - const { parsed: actual } = createUserConfig({ + const { parsed: actual } = parseUserConfig({ args: cli, }); for (const [key, value] of Object.entries(expected)) { @@ -575,7 +575,7 @@ describe("config", () => { it("should load a valid config file without troubles", () => { setVariable("MDB_MCP_CONFIG", CONFIG_FIXTURES.VALID); - const { warnings, error, parsed } = createUserConfig({ args: [] }); + const { warnings, error, parsed } = parseUserConfig({ args: [] }); expect(warnings).toHaveLength(0); expect(error).toBeUndefined(); @@ -585,7 +585,7 @@ describe("config", () => { it("should attempt loading config file with wrong value and exit", () => { setVariable("MDB_MCP_CONFIG", CONFIG_FIXTURES.WITH_INVALID_VALUE); - const { warnings, error, parsed } = createUserConfig({ args: [] }); + const { warnings, error, parsed } = parseUserConfig({ args: [] }); expect(warnings).toHaveLength(0); expect(error).toEqual(expect.stringContaining("loggers - Duplicate loggers found in config")); expect(parsed).toBeUndefined(); @@ -594,7 +594,7 @@ describe("config", () => { describe("through cli argument --config", () => { it("should load a valid config file without troubles", () => { - const { warnings, error, parsed } = createUserConfig({ args: ["--config", CONFIG_FIXTURES.VALID] }); + const { warnings, error, parsed } = parseUserConfig({ args: ["--config", CONFIG_FIXTURES.VALID] }); expect(warnings).toHaveLength(0); expect(error).toBeUndefined(); @@ -603,7 +603,7 @@ describe("config", () => { }); it("should attempt loading config file with wrong value and exit", () => { - const { warnings, error, parsed } = createUserConfig({ + const { warnings, error, parsed } = parseUserConfig({ args: ["--config", CONFIG_FIXTURES.WITH_INVALID_VALUE], }); expect(warnings).toHaveLength(0); @@ -622,7 +622,7 @@ describe("config", () => { it("positional argument takes precedence over all", () => { setVariable("MDB_MCP_CONNECTION_STRING", "mongodb://crazyhost1"); - const { parsed: actual } = createUserConfig({ + const { parsed: actual } = parseUserConfig({ args: [ "mongodb://crazyhost2", "--config", @@ -636,7 +636,7 @@ describe("config", () => { it("any cli argument takes precedence over env vars, config and defaults", () => { setVariable("MDB_MCP_CONNECTION_STRING", "mongodb://dummyhost"); - const { parsed } = createUserConfig({ + const { parsed } = parseUserConfig({ args: ["--config", CONFIG_FIXTURES.VALID, "--connectionString", "mongodb://host-from-cli"], }); expect(parsed?.connectionString).toBe("mongodb://host-from-cli"); @@ -644,19 +644,19 @@ describe("config", () => { it("any env var takes precedence over config and defaults", () => { setVariable("MDB_MCP_CONNECTION_STRING", "mongodb://dummyhost"); - const { parsed } = createUserConfig({ args: ["--config", CONFIG_FIXTURES.VALID] }); + const { parsed } = parseUserConfig({ args: ["--config", CONFIG_FIXTURES.VALID] }); expect(parsed?.connectionString).toBe("mongodb://dummyhost"); }); it("config file takes precedence over defaults", () => { - const { parsed } = createUserConfig({ args: ["--config", CONFIG_FIXTURES.VALID] }); + const { parsed } = parseUserConfig({ args: ["--config", CONFIG_FIXTURES.VALID] }); expect(parsed?.connectionString).toBe("mongodb://valid-json-localhost:1000"); }); }); describe("consolidation", () => { it("positional argument for url has precedence over --connectionString", () => { - const { parsed: actual } = createUserConfig({ + const { parsed: actual } = parseUserConfig({ args: ["mongodb://localhost", "--connectionString", "mongodb://toRemoveHost"], }); // the shell specifies directConnection=true and serverSelectionTimeoutMS=2000 by default @@ -666,7 +666,7 @@ describe("config", () => { }); it("positional argument is always considered", () => { - const { parsed: actual } = createUserConfig({ + const { parsed: actual } = parseUserConfig({ args: ["mongodb://localhost"], }); // the shell specifies directConnection=true and serverSelectionTimeoutMS=2000 by default @@ -679,21 +679,21 @@ describe("config", () => { describe("validation", () => { describe("transport", () => { it("should support http", () => { - const { parsed: actual } = createUserConfig({ + const { parsed: actual } = parseUserConfig({ args: ["--transport", "http"], }); expect(actual?.transport).toEqual("http"); }); it("should support stdio", () => { - const { parsed: actual } = createUserConfig({ + const { parsed: actual } = parseUserConfig({ args: ["--transport", "stdio"], }); expect(actual?.transport).toEqual("stdio"); }); it("should not support sse", () => { - const { error } = createUserConfig({ + const { error } = parseUserConfig({ args: ["--transport", "sse"], }); expect(error).toEqual( @@ -705,7 +705,7 @@ describe("config", () => { it("should not support arbitrary values", () => { const value = Math.random() + "transport"; - const { error } = createUserConfig({ + const { error } = parseUserConfig({ args: ["--transport", value], }); expect(error).toEqual( @@ -718,21 +718,21 @@ describe("config", () => { describe("telemetry", () => { it("can be enabled", () => { - const { parsed: actual } = createUserConfig({ + const { parsed: actual } = parseUserConfig({ args: ["--telemetry", "enabled"], }); expect(actual?.telemetry).toEqual("enabled"); }); it("can be disabled", () => { - const { parsed: actual } = createUserConfig({ + const { parsed: actual } = parseUserConfig({ args: ["--telemetry", "disabled"], }); expect(actual?.telemetry).toEqual("disabled"); }); it("should not support the boolean true value", () => { - const { error } = createUserConfig({ + const { error } = parseUserConfig({ args: ["--telemetry", "true"], }); expect(error).toEqual( @@ -743,7 +743,7 @@ describe("config", () => { }); it("should not support the boolean false value", () => { - const { error } = createUserConfig({ + const { error } = parseUserConfig({ args: ["--telemetry", "false"], }); expect(error).toEqual( @@ -755,7 +755,7 @@ describe("config", () => { it("should not support arbitrary values", () => { const value = Math.random() + "telemetry"; - const { error } = createUserConfig({ + const { error } = parseUserConfig({ args: ["--telemetry", value], }); expect(error).toEqual( @@ -768,7 +768,7 @@ describe("config", () => { describe("httpPort", () => { it("must be above 0", () => { - const { error } = createUserConfig({ + const { error } = parseUserConfig({ args: ["--httpPort", "-1"], }); expect(error).toEqual( @@ -779,7 +779,7 @@ describe("config", () => { }); it("must be below 65535 (OS limit)", () => { - const { error } = createUserConfig({ + const { error } = parseUserConfig({ args: ["--httpPort", "89527345"], }); expect(error).toEqual( @@ -790,7 +790,7 @@ describe("config", () => { }); it("should not support non numeric values", () => { - const { error } = createUserConfig({ + const { error } = parseUserConfig({ args: ["--httpPort", "portAventura"], }); expect(error).toEqual( @@ -801,7 +801,7 @@ describe("config", () => { }); it("should support numeric values", () => { - const { parsed: actual } = createUserConfig({ args: ["--httpPort", "8888"] }); + const { parsed: actual } = parseUserConfig({ args: ["--httpPort", "8888"] }); expect(actual?.httpPort).toEqual(8888); }); }); @@ -824,23 +824,23 @@ describe("config", () => { for (const { description, args, expectedError } of invalidLoggerTestCases) { it(description, () => { - const { error } = createUserConfig({ args }); + const { error } = parseUserConfig({ args }); expect(error).toEqual(expect.stringContaining(expectedError)); }); } it("allows mcp logger", () => { - const { parsed: actual } = createUserConfig({ args: ["--loggers", "mcp"] }); + const { parsed: actual } = parseUserConfig({ args: ["--loggers", "mcp"] }); expect(actual?.loggers).toEqual(["mcp"]); }); it("allows disk logger", () => { - const { parsed: actual } = createUserConfig({ args: ["--loggers", "disk"] }); + const { parsed: actual } = parseUserConfig({ args: ["--loggers", "disk"] }); expect(actual?.loggers).toEqual(["disk"]); }); it("allows stderr logger", () => { - const { parsed: actual } = createUserConfig({ args: ["--loggers", "stderr"] }); + const { parsed: actual } = parseUserConfig({ args: ["--loggers", "stderr"] }); expect(actual?.loggers).toEqual(["stderr"]); }); }); @@ -887,7 +887,7 @@ describe("keychain management", () => { for (const { cliArg, secretKind } of testCases) { it(`should register ${cliArg} as a secret of kind ${secretKind} in the root keychain`, () => { - createUserConfig({ args: [`--${cliArg}`, cliArg] }); + parseUserConfig({ args: [`--${cliArg}`, cliArg] }); expect(keychain.allSecrets).toEqual([{ value: cliArg, kind: secretKind }]); }); } From d90ea39922788da19a1d951a35fd9f4bce5941a0 Mon Sep 17 00:00:00 2001 From: gagik Date: Mon, 15 Dec 2025 11:11:26 +0100 Subject: [PATCH 3/8] chore: delete definition --- MCP_SERVER_LIBRARY.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/MCP_SERVER_LIBRARY.md b/MCP_SERVER_LIBRARY.md index 3b7746714..86b12d78f 100644 --- a/MCP_SERVER_LIBRARY.md +++ b/MCP_SERVER_LIBRARY.md @@ -817,12 +817,6 @@ Utility function to parse command-line arguments and environment variables into _Note: This is what MongoDB MCP server uses internally._ -```typescript -function parseUserConfig(options?: { - args?: string[]; -}): UserConfig; -``` - **Example:** ```typescript From b9961ab055fc6cfee12ce84de078900b3c842e13 Mon Sep 17 00:00:00 2001 From: gagik Date: Mon, 15 Dec 2025 11:12:52 +0100 Subject: [PATCH 4/8] chore: rename file --- src/common/config/{createUserConfig.ts => parseUserConfig.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/common/config/{createUserConfig.ts => parseUserConfig.ts} (100%) diff --git a/src/common/config/createUserConfig.ts b/src/common/config/parseUserConfig.ts similarity index 100% rename from src/common/config/createUserConfig.ts rename to src/common/config/parseUserConfig.ts From 93ef973fad5608380889dd4a5a9c5171ac62c5f0 Mon Sep 17 00:00:00 2001 From: gagik Date: Mon, 15 Dec 2025 11:16:16 +0100 Subject: [PATCH 5/8] make parserOptions optional --- src/common/config/parseUserConfig.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/config/parseUserConfig.ts b/src/common/config/parseUserConfig.ts index 3d3403700..2bd417110 100644 --- a/src/common/config/parseUserConfig.ts +++ b/src/common/config/parseUserConfig.ts @@ -36,7 +36,7 @@ export function parseUserConfig({ }: { args: string[]; overrides?: z4.ZodRawShape; - parserOptions: ParserOptions; + parserOptions?: ParserOptions; }): { warnings: string[]; parsed: UserConfig | undefined; From 666ab2bb9bfb99531e79660406cb59ff0ac67eeb Mon Sep 17 00:00:00 2001 From: gagik Date: Mon, 15 Dec 2025 11:25:26 +0100 Subject: [PATCH 6/8] chore: avoid yargs-parser dependency --- src/common/config/parseUserConfig.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/common/config/parseUserConfig.ts b/src/common/config/parseUserConfig.ts index 2bd417110..2d10a1936 100644 --- a/src/common/config/parseUserConfig.ts +++ b/src/common/config/parseUserConfig.ts @@ -10,9 +10,8 @@ import { UnknownArgumentError, } from "@mongosh/arg-parser/arg-parser"; import { z as z4 } from "zod/v4"; -import type yargsParser from "yargs-parser"; -export type ParserOptions = Partial; +export type ParserOptions = typeof defaultArgParserOptions; export const defaultParserOptions = { // This is the name of key that yargs-parser will look up in CLI From 3b4a437a87dd546640fa827e1e05470c7ea63374 Mon Sep 17 00:00:00 2001 From: gagik Date: Tue, 16 Dec 2025 17:34:32 +0100 Subject: [PATCH 7/8] chore: add deprecated parseArgsWithCliOptions --- MCP_SERVER_LIBRARY.md | 11 +++++++++-- src/lib.ts | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/MCP_SERVER_LIBRARY.md b/MCP_SERVER_LIBRARY.md index 86b12d78f..82dfd76cb 100644 --- a/MCP_SERVER_LIBRARY.md +++ b/MCP_SERVER_LIBRARY.md @@ -820,10 +820,17 @@ _Note: This is what MongoDB MCP server uses internally._ **Example:** ```typescript -import { parseUserConfig, StdioRunner } from "mongodb-mcp-server"; +import { parseUserConfig, StdioRunner, UserConfigSchema } from "mongodb-mcp-server"; // Parse config from process.argv and environment variables -const config = parseUserConfig(); +const config = parseUserConfig({ + args: process.argv.slice(2), + // You can optionally specify overrides for the config + // This can be used, for example, to set new defaults. + overrides: { + readOnly: UserConfigSchema.shape.readOnly.default(true), + } +}); const runner = new StdioRunner({ userConfig: config }); await runner.start(); diff --git a/src/lib.ts b/src/lib.ts index 54834addb..a87ded3af 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -2,6 +2,21 @@ export { Server, type ServerOptions } from "./server.js"; export { Session, type SessionOptions } from "./common/session.js"; export { type UserConfig, UserConfigSchema } from "./common/config/userConfig.js"; export { parseUserConfig, defaultParserOptions, type ParserOptions } from "./common/config/parseUserConfig.js"; + +import { parseUserConfig } from "./common/config/parseUserConfig.js"; +import type { UserConfig } from "./common/config/userConfig.js"; + +/** @deprecated Use `parseUserConfig` instead. */ +export function parseArgsWithCliOptions(cliArguments: string[]): { + warnings: string[]; + parsed: UserConfig | undefined; + error: string | undefined; +} { + return parseUserConfig({ + args: cliArguments, + }); +} + export { LoggerBase, type LogPayload, type LoggerType, type LogLevel } from "./common/logger.js"; export { StreamableHttpRunner } from "./transports/streamableHttp.js"; export { StdioRunner } from "./transports/stdio.js"; From 6faa450adf1de2e12f5838fffa49de5a1b318bfc Mon Sep 17 00:00:00 2001 From: gagik Date: Tue, 16 Dec 2025 17:38:24 +0100 Subject: [PATCH 8/8] chore: prettier --- MCP_SERVER_LIBRARY.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/MCP_SERVER_LIBRARY.md b/MCP_SERVER_LIBRARY.md index 82dfd76cb..7e69e4e9a 100644 --- a/MCP_SERVER_LIBRARY.md +++ b/MCP_SERVER_LIBRARY.md @@ -820,7 +820,11 @@ _Note: This is what MongoDB MCP server uses internally._ **Example:** ```typescript -import { parseUserConfig, StdioRunner, UserConfigSchema } from "mongodb-mcp-server"; +import { + parseUserConfig, + StdioRunner, + UserConfigSchema, +} from "mongodb-mcp-server"; // Parse config from process.argv and environment variables const config = parseUserConfig({ @@ -829,7 +833,7 @@ const config = parseUserConfig({ // This can be used, for example, to set new defaults. overrides: { readOnly: UserConfigSchema.shape.readOnly.default(true), - } + }, }); const runner = new StdioRunner({ userConfig: config });