Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 15 additions & 11 deletions MCP_SERVER_LIBRARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import {
Session,
UserConfig,
UserConfigSchema,
parseCliArgumentsAsUserConfig,
parseUserConfig,
StreamableHttpRunner,
StdioRunner,
TransportRunnerBase,
Expand Down Expand Up @@ -811,26 +811,30 @@ 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?: {
args?: string[];
helpers?: CreateUserConfigHelpers;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the concept of CreateUserConfigHelpers was unused.

}): UserConfig;
```

**Example:**

```typescript
import { parseCliArgumentsAsUserConfig, StdioRunner } from "mongodb-mcp-server";
import {
parseUserConfig,
StdioRunner,
UserConfigSchema,
} from "mongodb-mcp-server";

// Parse config from process.argv and environment variables
const config = parseCliArgumentsAsUserConfig();
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();
Expand Down
2 changes: 1 addition & 1 deletion eslint-rules/enforce-zod-v4.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,51 @@ 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";

export type ParserOptions = typeof defaultArgParserOptions;

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 parseUserConfig({
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking q: Which takes precedent: env vars or overrides?
I suspect env vars are applied to the UserConfiSchema before we get to this point and so overrides take precedent.

Copy link
Collaborator Author

@gagik gagik Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generally overrides are applied to the schema and in practice likely only going to be used to modify the default, i.e.

{
    readOnly: UserConfigSchema.shape.readOnly.default(true)
}

so any value (CLI, ENV) will override the default.

I will add some documentation to make it clearer

? z4.object({
...UserConfigSchema.shape,
...overrides,
})
: UserConfigSchema;

const { error: parseError, warnings, parsed } = parseUserConfigSources({ args, schema, parserOptions });

if (parseError) {
return { error: parseError, warnings, parsed: undefined };
Expand All @@ -40,7 +72,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) {
Expand All @@ -62,34 +94,29 @@ export function createUserConfig({ args }: { args: string[] }): {
};
}

function parseUserConfigSources(cliArguments: string[]): {
function parseUserConfigSources<T extends typeof UserConfigSchema>({
args,
schema = UserConfigSchema as T,
parserOptions,
}: {
args: string[];
schema: T;
parserOptions: ParserOptions;
}): {
error: string | undefined;
warnings: string[];
parsed: Partial<CliOptions & z4.infer<typeof UserConfigSchema>>;
parsed: Partial<CliOptions & z4.infer<T>>;
} {
let parsed: Partial<CliOptions & z4.infer<typeof UserConfigSchema>>;
let deprecated: Record<string, keyof UserConfig>;
let parsed: Partial<CliOptions & z4.infer<T>>;
let deprecated: Record<string, string>;
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<string, string>;

// Delete fileNames - this is a field populated by mongosh but not used by us.
delete parsed.fileNames;
Expand All @@ -112,7 +139,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.`;
}),
Expand Down
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -53,7 +53,7 @@ async function main(): Promise<void> {
error,
warnings,
parsed: config,
} = createUserConfig({
} = parseUserConfig({
args: process.argv.slice(2),
});

Expand Down
17 changes: 16 additions & 1 deletion src/lib.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
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";
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so I unfortunately missed that we're doing this; it's weird to have this discrepancy where our external export is named differently than our internal one. To me the idea that the name isn't straightforward was a code smell.

parseCliArgumentsAsUserConfig is inherently misleading though since by default it also parses environment variables. This can mislead users so it's best to rename it.

It is technically a library breaking change though.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then we anticipate the release with this work will bump major version, correct?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically, yes, though to avoid a major version bump, let's re-export with the old name and mark as deprecated?

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";
Expand Down
4 changes: 2 additions & 2 deletions src/transports/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ export type TransportRunnerConfig = {
* `UserConfig`.
*
* 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
* `UserConfig` object, you can use `parseUserConfig` function, also
* exported as `parseUserConfig` through MCP server library
* exports.
*
* Optionally, you can also use `UserConfigSchema` (available through MCP
Expand Down
Loading