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 ;
@@ -59,11 +154,11 @@ const defaults: UserConfig = {
59154 notificationTimeoutMs : 540000 , // 9 minutes
60155} ;
61156
62- export const config = {
63- ... defaults ,
64- ... getEnvConfig ( ) ,
65- ... getCliConfig ( ) ,
66- } ;
157+ export const config = setupUserConfig ( {
158+ defaults,
159+ cli : process . argv ,
160+ env : process . env ,
161+ } ) ;
67162
68163function getLocalDataPath ( ) : string {
69164 return process . platform === "win32"
@@ -83,7 +178,7 @@ function getExportsPath(): string {
83178// Gets the config supplied by the user as environment variables. The variable names
84179// are prefixed with `MDB_MCP_` and the keys match the UserConfig keys, but are converted
85180// to SNAKE_UPPER_CASE.
86- function getEnvConfig ( ) : Partial < UserConfig > {
181+ function parseEnvConfig ( env : Record < string , unknown > ) : Partial < UserConfig > {
87182 function setValue ( obj : Record < string , unknown > , path : string [ ] , value : string ) : void {
88183 const currentField = path . shift ( ) ;
89184 if ( ! currentField ) {
@@ -120,7 +215,7 @@ function getEnvConfig(): Partial<UserConfig> {
120215 }
121216
122217 const result : Record < string , unknown > = { } ;
123- const mcpVariables = Object . entries ( process . env ) . filter (
218+ const mcpVariables = Object . entries ( env ) . filter (
124219 ( [ key , value ] ) => value !== undefined && key . startsWith ( "MDB_MCP_" )
125220 ) as [ string , string ] [ ] ;
126221 for ( const [ key , value ] of mcpVariables ) {
@@ -139,9 +234,72 @@ function SNAKE_CASE_toCamelCase(str: string): string {
139234 return str . toLowerCase ( ) . replace ( / ( [ - _ ] [ a - z ] ) / g, ( group ) => group . toUpperCase ( ) . replace ( "_" , "" ) ) ;
140235}
141236
142- // Reads the cli args and parses them into a UserConfig object.
143- function getCliConfig ( ) : Partial < UserConfig > {
144- return argv ( process . argv . slice ( 2 ) , {
145- array : [ "disabledTools" , "loggers" ] ,
146- } ) as unknown as Partial < UserConfig > ;
237+ // Right now we have arguments that are not compatible with the format used in mongosh.
238+ // An example is using --connectionString and positional arguments.
239+ // We will consolidate them in a way where the mongosh format takes precedence.
240+ // We will warn users that previous configuration is deprecated in favour of
241+ // whatever is in mongosh.
242+ function parseCliConfig ( args : string [ ] ) : CliOptions {
243+ const programArgs = args . slice ( 2 ) ;
244+ const parsed = argv ( programArgs , OPTIONS ) as unknown as CliOptions &
245+ UserConfig & {
246+ _ ?: string [ ] ;
247+ } ;
248+
249+ const positionalArguments = parsed . _ ?? [ ] ;
250+ if ( ! parsed . nodb && isConnectionSpecifier ( positionalArguments [ 0 ] ) ) {
251+ parsed . connectionSpecifier = positionalArguments . shift ( ) ;
252+ }
253+
254+ delete parsed . _ ;
255+ return parsed ;
256+ }
257+
258+ function commaSeparatedToArray < T extends string [ ] > ( str : string | string [ ] | undefined ) : T {
259+ if ( str === undefined ) {
260+ return [ ] as unknown as T ;
261+ }
262+
263+ if ( ! Array . isArray ( str ) ) {
264+ return [ str ] as T ;
265+ }
266+
267+ if ( str . length === 0 ) {
268+ return str as T ;
269+ }
270+
271+ if ( str . length === 1 ) {
272+ if ( str [ 0 ] ?. indexOf ( "," ) === - 1 ) {
273+ return str as T ;
274+ } else {
275+ return str [ 0 ] ?. split ( "," ) . map ( ( e ) => e . trim ( ) ) as T ;
276+ }
277+ }
278+
279+ return str as T ;
280+ }
281+
282+ export function setupUserConfig ( {
283+ cli,
284+ env,
285+ defaults,
286+ } : {
287+ cli : string [ ] ;
288+ env : Record < string , unknown > ;
289+ defaults : Partial < UserConfig > ;
290+ } ) : UserConfig {
291+ const userConfig : UserConfig = {
292+ ...defaults ,
293+ ...parseEnvConfig ( env ) ,
294+ ...parseCliConfig ( cli ) ,
295+ } as UserConfig ;
296+
297+ userConfig . disabledTools = commaSeparatedToArray ( userConfig . disabledTools ) ;
298+ userConfig . loggers = commaSeparatedToArray ( userConfig . loggers ) ;
299+
300+ if ( userConfig . connectionString && userConfig . connectionSpecifier ) {
301+ delete userConfig . connectionString ;
302+ }
303+
304+ return userConfig ;
147305}
0 commit comments