1
1
import path from "path" ;
2
2
import os from "os" ;
3
3
import argv from "yargs-parser" ;
4
-
4
+ import type { CliOptions } from "@mongosh/arg-parser" ;
5
5
import { ReadConcernLevel , ReadPreferenceMode , W } from "mongodb" ;
6
6
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
+
7
102
export interface ConnectOptions {
8
103
readConcern : ReadConcernLevel ;
9
104
readPreference : ReadPreferenceMode ;
@@ -13,7 +108,7 @@ export interface ConnectOptions {
13
108
14
109
// If we decide to support non-string config options, we'll need to extend the mechanism for parsing
15
110
// env variables.
16
- export interface UserConfig {
111
+ export interface UserConfig extends CliOptions {
17
112
apiBaseUrl : string ;
18
113
apiClientId ?: string ;
19
114
apiClientSecret ?: string ;
@@ -53,11 +148,11 @@ const defaults: UserConfig = {
53
148
notificationTimeoutMs : 540000 , // 9 minutes
54
149
} ;
55
150
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
+ } ) ;
61
156
62
157
function getLogPath ( ) : string {
63
158
const localDataPath =
@@ -73,7 +168,7 @@ function getLogPath(): string {
73
168
// Gets the config supplied by the user as environment variables. The variable names
74
169
// are prefixed with `MDB_MCP_` and the keys match the UserConfig keys, but are converted
75
170
// to SNAKE_UPPER_CASE.
76
- function getEnvConfig ( ) : Partial < UserConfig > {
171
+ function parseEnvConfig ( env : Record < string , unknown > ) : Partial < UserConfig > {
77
172
function setValue ( obj : Record < string , unknown > , path : string [ ] , value : string ) : void {
78
173
const currentField = path . shift ( ) ;
79
174
if ( ! currentField ) {
@@ -110,7 +205,7 @@ function getEnvConfig(): Partial<UserConfig> {
110
205
}
111
206
112
207
const result : Record < string , unknown > = { } ;
113
- const mcpVariables = Object . entries ( process . env ) . filter (
208
+ const mcpVariables = Object . entries ( env ) . filter (
114
209
( [ key , value ] ) => value !== undefined && key . startsWith ( "MDB_MCP_" )
115
210
) as [ string , string ] [ ] ;
116
211
for ( const [ key , value ] of mcpVariables ) {
@@ -129,9 +224,72 @@ function SNAKE_CASE_toCamelCase(str: string): string {
129
224
return str . toLowerCase ( ) . replace ( / ( [ - _ ] [ a - z ] ) / g, ( group ) => group . toUpperCase ( ) . replace ( "_" , "" ) ) ;
130
225
}
131
226
132
- // Reads the cli args and parses them into a UserConfig object.
133
- function getCliConfig ( ) {
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 ;
137
295
}
0 commit comments