11import path from "path" ;
22import os from "os" ;
33import argv from "yargs-parser" ;
4-
4+ import type { CliOptions } from "@mongosh/arg-parser" ;
55import { ReadConcernLevel , ReadPreferenceMode , W } from "mongodb" ;
66
7+ // From: https://github.com/mongodb-js/mongosh/blob/main/packages/cli-repl/src/arg-parser.ts
8+ const OPTIONS = {
9+ string : [
10+ "apiBaseUrl" ,
11+ "apiClientId" ,
12+ "apiClientSecret" ,
13+ "connectionString" ,
14+ "httpHost" ,
15+ "httpPort" ,
16+ "idleTimeoutMs" ,
17+ "logPath" ,
18+ "notificationTimeoutMs" ,
19+ "telemetry" ,
20+ "transport" ,
21+ "apiVersion" ,
22+ "authenticationDatabase" ,
23+ "authenticationMechanism" ,
24+ "browser" ,
25+ "db" ,
26+ "gssapiHostName" ,
27+ "gssapiServiceName" ,
28+ "host" ,
29+ "oidcFlows" ,
30+ "oidcRedirectUri" ,
31+ "password" ,
32+ "port" ,
33+ "sslCAFile" ,
34+ "sslCRLFile" ,
35+ "sslCertificateSelector" ,
36+ "sslDisabledProtocols" ,
37+ "sslPEMKeyFile" ,
38+ "sslPEMKeyPassword" ,
39+ "sspiHostnameCanonicalization" ,
40+ "sspiRealmOverride" ,
41+ "tlsCAFile" ,
42+ "tlsCRLFile" ,
43+ "tlsCertificateKeyFile" ,
44+ "tlsCertificateKeyFilePassword" ,
45+ "tlsCertificateSelector" ,
46+ "tlsDisabledProtocols" ,
47+ "username" ,
48+ ] ,
49+ boolean : [
50+ "apiDeprecationErrors" ,
51+ "apiStrict" ,
52+ "help" ,
53+ "indexCheck" ,
54+ "ipv6" ,
55+ "nodb" ,
56+ "oidcIdTokenAsAccessToken" ,
57+ "oidcNoNonce" ,
58+ "oidcTrustedEndpoint" ,
59+ "readOnly" ,
60+ "retryWrites" ,
61+ "ssl" ,
62+ "sslAllowInvalidCertificates" ,
63+ "sslAllowInvalidHostnames" ,
64+ "sslFIPSMode" ,
65+ "tls" ,
66+ "tlsAllowInvalidCertificates" ,
67+ "tlsAllowInvalidHostnames" ,
68+ "tlsFIPSMode" ,
69+ "tlsUseSystemCA" ,
70+ "version" ,
71+ ] ,
72+ array : [ "disabledTools" , "loggers" ] ,
73+ alias : {
74+ h : "help" ,
75+ p : "password" ,
76+ u : "username" ,
77+ "build-info" : "buildInfo" ,
78+ browser : "browser" ,
79+ oidcDumpTokens : "oidcDumpTokens" ,
80+ oidcRedirectUrl : "oidcRedirectUri" ,
81+ oidcIDTokenAsAccessToken : "oidcIdTokenAsAccessToken" ,
82+ } ,
83+ configuration : {
84+ "camel-case-expansion" : false ,
85+ "unknown-options-as-args" : true ,
86+ "parse-positional-numbers" : false ,
87+ "parse-numbers" : false ,
88+ "greedy-arrays" : true ,
89+ "short-option-groups" : false ,
90+ } ,
91+ } ;
92+
93+ function isConnectionSpecifier ( arg : string | undefined ) : boolean {
94+ return (
95+ arg !== undefined &&
96+ ( arg . startsWith ( "mongodb://" ) ||
97+ arg . startsWith ( "mongodb+srv://" ) ||
98+ ! ( arg . endsWith ( ".js" ) || arg . endsWith ( ".mongodb" ) ) )
99+ ) ;
100+ }
101+
7102export interface ConnectOptions {
8103 readConcern : ReadConcernLevel ;
9104 readPreference : ReadPreferenceMode ;
@@ -13,7 +108,7 @@ export interface ConnectOptions {
13108
14109// If we decide to support non-string config options, we'll need to extend the mechanism for parsing
15110// env variables.
16- export interface UserConfig {
111+ export interface UserConfig extends CliOptions {
17112 apiBaseUrl : string ;
18113 apiClientId ?: string ;
19114 apiClientSecret ?: string ;
@@ -53,11 +148,11 @@ const defaults: UserConfig = {
53148 notificationTimeoutMs : 540000 , // 9 minutes
54149} ;
55150
56- export const config = {
57- ... defaults ,
58- ... getEnvConfig ( ) ,
59- ... getCliConfig ( ) ,
60- } ;
151+ export const config = setupUserConfig ( {
152+ defaults,
153+ cli : process . argv ,
154+ env : process . env ,
155+ } ) ;
61156
62157function getLogPath ( ) : string {
63158 const localDataPath =
@@ -73,7 +168,7 @@ function getLogPath(): string {
73168// Gets the config supplied by the user as environment variables. The variable names
74169// are prefixed with `MDB_MCP_` and the keys match the UserConfig keys, but are converted
75170// to SNAKE_UPPER_CASE.
76- function getEnvConfig ( ) : Partial < UserConfig > {
171+ function parseEnvConfig ( env : Record < string , unknown > ) : Partial < UserConfig > {
77172 function setValue ( obj : Record < string , unknown > , path : string [ ] , value : string ) : void {
78173 const currentField = path . shift ( ) ;
79174 if ( ! currentField ) {
@@ -110,7 +205,7 @@ function getEnvConfig(): Partial<UserConfig> {
110205 }
111206
112207 const result : Record < string , unknown > = { } ;
113- const mcpVariables = Object . entries ( process . env ) . filter (
208+ const mcpVariables = Object . entries ( env ) . filter (
114209 ( [ key , value ] ) => value !== undefined && key . startsWith ( "MDB_MCP_" )
115210 ) as [ string , string ] [ ] ;
116211 for ( const [ key , value ] of mcpVariables ) {
@@ -129,9 +224,72 @@ function SNAKE_CASE_toCamelCase(str: string): string {
129224 return str . toLowerCase ( ) . replace ( / ( [ - _ ] [ a - z ] ) / g, ( group ) => group . toUpperCase ( ) . replace ( "_" , "" ) ) ;
130225}
131226
132- // Reads the cli args and parses them into a UserConfig object.
133- function getCliConfig ( ) : Partial < UserConfig > {
134- return argv ( process . argv . slice ( 2 ) , {
135- array : [ "disabledTools" , "loggers" ] ,
136- } ) as unknown as Partial < UserConfig > ;
227+ // Right now we have arguments that are not compatible with the format used in mongosh.
228+ // An example is using --connectionString and positional arguments.
229+ // We will consolidate them in a way where the mongosh format takes precedence.
230+ // We will warn users that previous configuration is deprecated in favour of
231+ // whatever is in mongosh.
232+ function parseCliConfig ( args : string [ ] ) : CliOptions {
233+ const programArgs = args . slice ( 2 ) ;
234+ const parsed = argv ( programArgs , OPTIONS ) as unknown as CliOptions &
235+ UserConfig & {
236+ _ ?: string [ ] ;
237+ } ;
238+
239+ const positionalArguments = parsed . _ ?? [ ] ;
240+ if ( ! parsed . nodb && isConnectionSpecifier ( positionalArguments [ 0 ] ) ) {
241+ parsed . connectionSpecifier = positionalArguments . shift ( ) ;
242+ }
243+
244+ delete parsed . _ ;
245+ return parsed ;
246+ }
247+
248+ function commaSeparatedToArray < T extends string [ ] > ( str : string | string [ ] | undefined ) : T {
249+ if ( str === undefined ) {
250+ return [ ] as unknown as T ;
251+ }
252+
253+ if ( ! Array . isArray ( str ) ) {
254+ return [ str ] as T ;
255+ }
256+
257+ if ( str . length === 0 ) {
258+ return str as T ;
259+ }
260+
261+ if ( str . length === 1 ) {
262+ if ( str [ 0 ] ?. indexOf ( "," ) === - 1 ) {
263+ return str as T ;
264+ } else {
265+ return str [ 0 ] ?. split ( "," ) . map ( ( e ) => e . trim ( ) ) as T ;
266+ }
267+ }
268+
269+ return str as T ;
270+ }
271+
272+ export function setupUserConfig ( {
273+ cli,
274+ env,
275+ defaults,
276+ } : {
277+ cli : string [ ] ;
278+ env : Record < string , unknown > ;
279+ defaults : Partial < UserConfig > ;
280+ } ) : UserConfig {
281+ const userConfig : UserConfig = {
282+ ...defaults ,
283+ ...parseEnvConfig ( env ) ,
284+ ...parseCliConfig ( cli ) ,
285+ } as UserConfig ;
286+
287+ userConfig . disabledTools = commaSeparatedToArray ( userConfig . disabledTools ) ;
288+ userConfig . loggers = commaSeparatedToArray ( userConfig . loggers ) ;
289+
290+ if ( userConfig . connectionString && userConfig . connectionSpecifier ) {
291+ delete userConfig . connectionString ;
292+ }
293+
294+ return userConfig ;
137295}
0 commit comments