1- import { driverOptions } from "./config.js" ;
1+ import { config , UserConfig , driverOptions } from "./config.js" ;
22import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver" ;
33import EventEmitter from "events" ;
44import { setAppNameParamIfMissing } from "../helpers/connectionOptions.js" ;
55import { packageInfo } from "./packageInfo.js" ;
66import ConnectionString from "mongodb-connection-string-url" ;
7- import { MongoClientOptions } from "mongodb" ;
7+ import { MongoClientOptions , OIDCCallbackParams } from "mongodb" ;
88import { ErrorCodes , MongoDBError } from "./errors.js" ;
9+ import type { MongoshBus } from "@mongosh/types" ;
10+ import { CompositeLogger , LogId } from "./logger.js" ;
11+
12+ // https://github.com/mongodb-js/oidc-plugin/blob/main/src/types.ts
13+ const MONGODB_OIDC_CLIENT_ERROR_EVENTS = [
14+ "mongodb-oidc-plugin:deserialization-failed" ,
15+ "mongodb-oidc-plugin:local-listen-failed" ,
16+ "mongodb-oidc-plugin:auth-attempt-failed" ,
17+ "mongodb-oidc-plugin:refresh-failed" ,
18+ "mongodb-oidc-plugin:auth-failed" ,
19+ "mongodb-oidc-plugin:outbound-http-request-failed" ,
20+ ] as const ;
921
1022export interface AtlasClusterConnectionInfo {
1123 username : string ;
@@ -67,11 +79,25 @@ export interface ConnectionManagerEvents {
6779
6880export class ConnectionManager extends EventEmitter < ConnectionManagerEvents > {
6981 private state : AnyConnectionState ;
82+ private bus : MongoshBus ;
7083
71- constructor ( ) {
84+ constructor (
85+ private logger : CompositeLogger ,
86+ bus ?: MongoshBus
87+ ) {
7288 super ( ) ;
7389
90+ this . bus = bus ?? new EventEmitter ( ) ;
7491 this . state = { tag : "disconnected" } ;
92+
93+ for ( const clientErrorEvent of MONGODB_OIDC_CLIENT_ERROR_EVENTS ) {
94+ this . bus . on ( clientErrorEvent , this . onOidcClientErrorCallback . bind ( this , clientErrorEvent ) ) ;
95+ }
96+
97+ this . bus . on ( "mongodb-oidc-plugin:received-server-params" , this . onOidcReceivedServerParameters . bind ( this ) ) ;
98+ this . bus . on ( "mongodb-oidc-plugin:auth-failed" , this . onOidcAuthenticationFailed . bind ( this ) ) ;
99+ this . bus . on ( "mongodb-oidc-plugin:auth-succeeded" , this . onOidcAuthSucceeded . bind ( this ) ) ;
100+ this . bus . on ( "mongodb-oidc-plugin:notify-device-flow" , this . onOidcNotifyDeviceFlow . bind ( this ) ) ;
75101 }
76102
77103 async connect ( settings : ConnectionSettings ) : Promise < AnyConnectionState > {
@@ -89,11 +115,16 @@ export class ConnectionManager extends EventEmitter<ConnectionManagerEvents> {
89115 defaultAppName : `${ packageInfo . mcpServerName } ${ packageInfo . version } ` ,
90116 } ) ;
91117
92- serviceProvider = await NodeDriverServiceProvider . connect ( settings . connectionString , {
93- productDocsLink : "https://github.com/mongodb-js/mongodb-mcp-server/" ,
94- productName : "MongoDB MCP" ,
95- ...driverOptions ,
96- } ) ;
118+ serviceProvider = await NodeDriverServiceProvider . connect (
119+ settings . connectionString ,
120+ {
121+ productDocsLink : "https://github.com/mongodb-js/mongodb-mcp-server/" ,
122+ productName : "MongoDB MCP" ,
123+ ...driverOptions ,
124+ } ,
125+ undefined ,
126+ this . bus
127+ ) ;
97128 } catch ( error : unknown ) {
98129 const errorReason = error instanceof Error ? error . message : `${ error as string } ` ;
99130 this . changeState ( "connection-errored" , {
@@ -111,7 +142,7 @@ export class ConnectionManager extends EventEmitter<ConnectionManagerEvents> {
111142 tag : "connected" ,
112143 connectedAtlasCluster : settings . atlas ,
113144 serviceProvider,
114- connectionStringAuthType : ConnectionManager . inferConnectionTypeFromSettings ( settings ) ,
145+ connectionStringAuthType : ConnectionManager . inferConnectionTypeFromSettings ( config , settings ) ,
115146 } ) ;
116147 } catch ( error : unknown ) {
117148 const errorReason = error instanceof Error ? error . message : `${ error as string } ` ;
@@ -157,13 +188,92 @@ export class ConnectionManager extends EventEmitter<ConnectionManagerEvents> {
157188 return newState ;
158189 }
159190
160- static inferConnectionTypeFromSettings ( settings : ConnectionSettings ) : ConnectionStringAuthType {
191+ /**
192+ * This handles OIDC errors happening at the client side. There is a difference between errors happening
193+ * here and rejections from the server, so we will treat them differently.
194+ */
195+ private onOidcClientErrorCallback ( event : string , eventData : { error : string } ) : void {
196+ if ( this . state . tag === "connecting" && this . state . connectionStringAuthType ?. startsWith ( "oidc" ) ) {
197+ this . changeState ( "connection-errored" , { tag : "errored" , errorReason : eventData . error } ) ;
198+ }
199+
200+ // If we are not in the connecting state, we really can't do much with this. It might be a
201+ // transitional error (connectivity issues), a refresh token issue (server side configuration),
202+ // anything. Log the error to get some context later for debugging.
203+ this . logger . error ( {
204+ id : LogId . oidcClientError ,
205+ context : event ,
206+ message : `Error during OIDC flow, at event ${ event } : ${ eventData . error } ` ,
207+ } ) ;
208+ }
209+
210+ private onOidcReceivedServerParameters ( eventData : OIDCCallbackParams ) : void {
211+ if ( this . state . tag === "connecting" && this . state . connectionStringAuthType ?. startsWith ( "oidc" ) ) {
212+ this . changeState ( "connection-succeeded" , { ...this . state , tag : "connected" } ) ;
213+ }
214+
215+ this . logger . info ( {
216+ id : LogId . oidcFlow ,
217+ context : "mongodb-oidc-plugin:received-server-parameters" ,
218+ message : `Received server parameters successfully: ${ JSON . stringify ( eventData ) } ` ,
219+ } ) ;
220+ }
221+
222+ private onOidcAuthenticationFailed ( eventData : { error : string } ) : void {
223+ if ( this . state . tag === "connecting" && this . state . connectionStringAuthType ?. startsWith ( "oidc" ) ) {
224+ this . changeState ( "connection-succeeded" , { ...this . state , tag : "connected" } ) ;
225+ }
226+
227+ this . logger . error ( {
228+ id : LogId . oidcAuthFailed ,
229+ context : "mongodb-oidc-plugin:authentication-failed" ,
230+ message : `Could not authenticate: ${ eventData . error } ` ,
231+ } ) ;
232+ }
233+
234+ private onOidcAuthSucceeded ( ) : void {
235+ if ( this . state . tag === "connecting" && this . state . connectionStringAuthType ?. startsWith ( "oidc" ) ) {
236+ this . changeState ( "connection-succeeded" , { ...this . state , tag : "connected" } ) ;
237+ }
238+
239+ this . logger . info ( {
240+ id : LogId . oidcFlow ,
241+ context : "mongodb-oidc-plugin:auth-succeeded" ,
242+ message : "Authenticated successfully." ,
243+ } ) ;
244+ }
245+
246+ private onOidcNotifyDeviceFlow ( ) : void {
247+ if ( this . state . tag === "connecting" && this . state . connectionStringAuthType ?. startsWith ( "oidc" ) ) {
248+ this . changeState ( "connection-requested" , {
249+ ...this . state ,
250+ tag : "connecting" ,
251+ connectionStringAuthType : "oidc-device-flow" ,
252+ } ) ;
253+ }
254+
255+ this . logger . info ( {
256+ id : LogId . oidcFlow ,
257+ context : "mongodb-oidc-plugin:notify-device-flow" ,
258+ message : "OIDC Flow changed automatically to device flow." ,
259+ } ) ;
260+ }
261+
262+ static inferConnectionTypeFromSettings ( config : UserConfig , settings : ConnectionSettings ) : ConnectionStringAuthType {
161263 const connString = new ConnectionString ( settings . connectionString ) ;
162264 const searchParams = connString . typedSearchParams < MongoClientOptions > ( ) ;
163265
164266 switch ( searchParams . get ( "authMechanism" ) ) {
165267 case "MONGODB-OIDC" : {
166- return "oidc-auth-flow" ; // TODO: depending on if we don't have a --browser later it can be oidc-device-flow
268+ if ( config . transport === "stdio" && config . browser ) {
269+ return "oidc-auth-flow" ;
270+ }
271+
272+ if ( config . transport === "http" && config . httpHost === "127.0.0.1" && config . browser ) {
273+ return "oidc-auth-flow" ;
274+ }
275+
276+ return "oidc-device-flow" ;
167277 }
168278 case "MONGODB-X509" :
169279 return "x.509" ;
0 commit comments