• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mongodb-js / mongodb-mcp-server / 17158105595

22 Aug 2025 02:33PM UTC coverage: 72.664% (-9.4%) from 82.024%
17158105595

push

github

web-flow
chore(deps): bump the npm_and_yarn group with 2 updates (#415)

749 of 884 branches covered (84.73%)

Branch coverage included in aggregate %.

3730 of 5280 relevant lines covered (70.64%)

64.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.02
/src/common/connectionManager.ts
1
import { UserConfig, DriverOptions } from "./config.js";
2
import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver";
2✔
3
import EventEmitter from "events";
2✔
4
import { setAppNameParamIfMissing } from "../helpers/connectionOptions.js";
2✔
5
import { packageInfo } from "./packageInfo.js";
2✔
6
import ConnectionString from "mongodb-connection-string-url";
2✔
7
import { MongoClientOptions } from "mongodb";
8
import { ErrorCodes, MongoDBError } from "./errors.js";
2✔
9
import { DeviceId } from "../helpers/deviceId.js";
10
import { AppNameComponents } from "../helpers/connectionOptions.js";
11
import { CompositeLogger, LogId } from "./logger.js";
2✔
12
import { ConnectionInfo, generateConnectionInfoFromCliArgs } from "@mongosh/arg-parser";
2✔
13

14
export interface AtlasClusterConnectionInfo {
15
    username: string;
16
    projectId: string;
17
    clusterName: string;
18
    expiryDate: Date;
19
}
20

21
export interface ConnectionSettings {
22
    connectionString: string;
23
    atlas?: AtlasClusterConnectionInfo;
24
}
25

26
type ConnectionTag = "connected" | "connecting" | "disconnected" | "errored";
27
type OIDCConnectionAuthType = "oidc-auth-flow" | "oidc-device-flow";
28
export type ConnectionStringAuthType = "scram" | "ldap" | "kerberos" | OIDCConnectionAuthType | "x.509";
29

30
export interface ConnectionState {
31
    tag: ConnectionTag;
32
    connectionStringAuthType?: ConnectionStringAuthType;
33
    connectedAtlasCluster?: AtlasClusterConnectionInfo;
34
}
35

36
export interface ConnectionStateConnected extends ConnectionState {
37
    tag: "connected";
38
    serviceProvider: NodeDriverServiceProvider;
39
}
40

41
export interface ConnectionStateConnecting extends ConnectionState {
42
    tag: "connecting";
43
    serviceProvider: NodeDriverServiceProvider;
44
    oidcConnectionType: OIDCConnectionAuthType;
45
    oidcLoginUrl?: string;
46
    oidcUserCode?: string;
47
}
48

49
export interface ConnectionStateDisconnected extends ConnectionState {
50
    tag: "disconnected";
51
}
52

53
export interface ConnectionStateErrored extends ConnectionState {
54
    tag: "errored";
55
    errorReason: string;
56
}
57

58
export type AnyConnectionState =
59
    | ConnectionStateConnected
60
    | ConnectionStateConnecting
61
    | ConnectionStateDisconnected
62
    | ConnectionStateErrored;
63

64
export interface ConnectionManagerEvents {
65
    "connection-requested": [AnyConnectionState];
66
    "connection-succeeded": [ConnectionStateConnected];
67
    "connection-timed-out": [ConnectionStateErrored];
68
    "connection-closed": [ConnectionStateDisconnected];
69
    "connection-errored": [ConnectionStateErrored];
70
}
71

72
export class ConnectionManager extends EventEmitter<ConnectionManagerEvents> {
2✔
73
    private state: AnyConnectionState;
74
    private deviceId: DeviceId;
75
    private clientName: string;
76
    private bus: EventEmitter;
77

78
    constructor(
2✔
79
        private userConfig: UserConfig,
90✔
80
        private driverOptions: DriverOptions,
90✔
81
        private logger: CompositeLogger,
90✔
82
        deviceId: DeviceId,
90✔
83
        bus?: EventEmitter
90✔
84
    ) {
90✔
85
        super();
90✔
86

87
        this.bus = bus ?? new EventEmitter();
90✔
88
        this.state = { tag: "disconnected" };
90✔
89

90
        this.bus.on("mongodb-oidc-plugin:auth-failed", this.onOidcAuthFailed.bind(this));
90✔
91
        this.bus.on("mongodb-oidc-plugin:auth-succeeded", this.onOidcAuthSucceeded.bind(this));
90✔
92

93
        this.deviceId = deviceId;
90✔
94
        this.clientName = "unknown";
90✔
95
    }
90✔
96

97
    setClientName(clientName: string): void {
2✔
98
        this.clientName = clientName;
76✔
99
    }
76✔
100

101
    async connect(settings: ConnectionSettings): Promise<AnyConnectionState> {
2✔
102
        this.emit("connection-requested", this.state);
410✔
103

104
        if (this.state.tag === "connected" || this.state.tag === "connecting") {
410✔
105
            await this.disconnect();
20✔
106
        }
20✔
107

108
        let serviceProvider: NodeDriverServiceProvider;
410✔
109
        let connectionInfo: ConnectionInfo;
410✔
110

111
        try {
410✔
112
            settings = { ...settings };
410✔
113
            const appNameComponents: AppNameComponents = {
410✔
114
                appName: `${packageInfo.mcpServerName} ${packageInfo.version}`,
410✔
115
                deviceId: this.deviceId.get(),
410✔
116
                clientName: this.clientName,
410✔
117
            };
410✔
118

119
            settings.connectionString = await setAppNameParamIfMissing({
410✔
120
                connectionString: settings.connectionString,
410✔
121
                components: appNameComponents,
410✔
122
            });
410✔
123

124
            connectionInfo = generateConnectionInfoFromCliArgs({
410✔
125
                ...this.userConfig,
410✔
126
                ...this.driverOptions,
410✔
127
                connectionSpecifier: settings.connectionString,
410✔
128
            });
410✔
129

130
            if (connectionInfo.driverOptions.oidc) {
410✔
131
                connectionInfo.driverOptions.oidc.allowedFlows ??= ["auth-code"];
14✔
132
                connectionInfo.driverOptions.oidc.notifyDeviceFlow ??= this.onOidcNotifyDeviceFlow.bind(this);
14✔
133
            }
14✔
134

135
            connectionInfo.driverOptions.proxy ??= { useEnvironmentVariableProxies: true };
410✔
136
            connectionInfo.driverOptions.applyProxyToOIDC ??= true;
410✔
137

138
            serviceProvider = await NodeDriverServiceProvider.connect(
410✔
139
                connectionInfo.connectionString,
410✔
140
                {
410✔
141
                    productDocsLink: "https://github.com/mongodb-js/mongodb-mcp-server/",
410✔
142
                    productName: "MongoDB MCP",
410✔
143
                    ...connectionInfo.driverOptions,
410✔
144
                },
410✔
145
                undefined,
410✔
146
                this.bus
410✔
147
            );
410✔
148
        } catch (error: unknown) {
410✔
149
            const errorReason = error instanceof Error ? error.message : `${error as string}`;
10!
150
            this.changeState("connection-errored", {
10✔
151
                tag: "errored",
10✔
152
                errorReason,
10✔
153
                connectedAtlasCluster: settings.atlas,
10✔
154
            });
10✔
155
            throw new MongoDBError(ErrorCodes.MisconfiguredConnectionString, errorReason);
10✔
156
        }
10✔
157

158
        try {
400✔
159
            const connectionType = ConnectionManager.inferConnectionTypeFromSettings(this.userConfig, connectionInfo);
400✔
160
            if (connectionType.startsWith("oidc")) {
410✔
161
                void this.pingAndForget(serviceProvider);
14✔
162

163
                return this.changeState("connection-requested", {
14✔
164
                    tag: "connecting",
14✔
165
                    connectedAtlasCluster: settings.atlas,
14✔
166
                    serviceProvider,
14✔
167
                    connectionStringAuthType: connectionType,
14✔
168
                    oidcConnectionType: connectionType as OIDCConnectionAuthType,
14✔
169
                });
14✔
170
            }
14✔
171

172
            await serviceProvider?.runCommand?.("admin", { hello: 1 });
386✔
173

174
            return this.changeState("connection-succeeded", {
386✔
175
                tag: "connected",
386✔
176
                connectedAtlasCluster: settings.atlas,
386✔
177
                serviceProvider,
386✔
178
                connectionStringAuthType: connectionType,
386✔
179
            });
386✔
180
        } catch (error: unknown) {
396!
181
            const errorReason = error instanceof Error ? error.message : `${error as string}`;
×
182
            this.changeState("connection-errored", {
×
183
                tag: "errored",
×
184
                errorReason,
×
185
                connectedAtlasCluster: settings.atlas,
×
186
            });
×
187
            throw new MongoDBError(ErrorCodes.NotConnectedToMongoDB, errorReason);
×
188
        }
×
189
    }
410✔
190

191
    async disconnect(): Promise<ConnectionStateDisconnected | ConnectionStateErrored> {
2✔
192
        if (this.state.tag === "disconnected" || this.state.tag === "errored") {
816✔
193
            return this.state;
430✔
194
        }
430✔
195

196
        if (this.state.tag === "connected" || this.state.tag === "connecting") {
816!
197
            try {
386✔
198
                await this.state.serviceProvider?.close(true);
386✔
199
            } finally {
386✔
200
                this.changeState("connection-closed", {
386✔
201
                    tag: "disconnected",
386✔
202
                });
386✔
203
            }
386✔
204
        }
386✔
205

206
        return { tag: "disconnected" };
386✔
207
    }
816✔
208

209
    get currentConnectionState(): AnyConnectionState {
2✔
210
        return this.state;
3,418✔
211
    }
3,418✔
212

213
    changeState<Event extends keyof ConnectionManagerEvents, State extends ConnectionManagerEvents[Event][0]>(
2✔
214
        event: Event,
852✔
215
        newState: State
852✔
216
    ): State {
852✔
217
        this.state = newState;
852✔
218
        // TypeScript doesn't seem to be happy with the spread operator and generics
219
        // eslint-disable-next-line
220
        this.emit(event, ...([newState] as any));
852✔
221
        return newState;
852✔
222
    }
852✔
223

224
    private onOidcAuthFailed(error: unknown): void {
2✔
225
        if (this.state.tag === "connecting" && this.state.connectionStringAuthType?.startsWith("oidc")) {
×
226
            void this.disconnectOnOidcError(error);
×
227
        }
×
228
    }
×
229

230
    private onOidcAuthSucceeded(): void {
2✔
231
        if (this.state.tag === "connecting" && this.state.connectionStringAuthType?.startsWith("oidc")) {
18✔
232
            this.changeState("connection-succeeded", { ...this.state, tag: "connected" });
14✔
233
        }
14✔
234

235
        this.logger.info({
18✔
236
            id: LogId.oidcFlow,
18✔
237
            context: "mongodb-oidc-plugin:auth-succeeded",
18✔
238
            message: "Authenticated successfully.",
18✔
239
        });
18✔
240
    }
18✔
241

242
    private onOidcNotifyDeviceFlow(flowInfo: { verificationUrl: string; userCode: string }): void {
2✔
243
        if (this.state.tag === "connecting" && this.state.connectionStringAuthType?.startsWith("oidc")) {
2✔
244
            this.changeState("connection-requested", {
2✔
245
                ...this.state,
2✔
246
                tag: "connecting",
2✔
247
                connectionStringAuthType: "oidc-device-flow",
2✔
248
                oidcLoginUrl: flowInfo.verificationUrl,
2✔
249
                oidcUserCode: flowInfo.userCode,
2✔
250
            });
2✔
251
        }
2✔
252

253
        this.logger.info({
2✔
254
            id: LogId.oidcFlow,
2✔
255
            context: "mongodb-oidc-plugin:notify-device-flow",
2✔
256
            message: "OIDC Flow changed automatically to device flow.",
2✔
257
        });
2✔
258
    }
2✔
259

260
    static inferConnectionTypeFromSettings(
2✔
261
        config: UserConfig,
418✔
262
        settings: { connectionString: string }
418✔
263
    ): ConnectionStringAuthType {
418✔
264
        const connString = new ConnectionString(settings.connectionString);
418✔
265
        const searchParams = connString.typedSearchParams<MongoClientOptions>();
418✔
266

267
        switch (searchParams.get("authMechanism")) {
418✔
268
            case "MONGODB-OIDC": {
418✔
269
                if (config.transport === "stdio" && config.browser) {
22✔
270
                    return "oidc-auth-flow";
14✔
271
                }
14✔
272

273
                if (config.transport === "http" && config.httpHost === "127.0.0.1" && config.browser) {
22✔
274
                    return "oidc-auth-flow";
2✔
275
                }
2✔
276

277
                return "oidc-device-flow";
6✔
278
            }
6✔
279
            case "MONGODB-X509":
418✔
280
                return "x.509";
2✔
281
            case "GSSAPI":
418✔
282
                return "kerberos";
2✔
283
            case "PLAIN":
418✔
284
                if (searchParams.get("authSource") === "$external") {
4✔
285
                    return "ldap";
2✔
286
                }
2✔
287
                return "scram";
2✔
288
            // default should catch also null, but eslint complains
289
            // about it.
290
            case null:
418✔
291
            default:
418✔
292
                return "scram";
388✔
293
        }
418✔
294
    }
418✔
295

296
    private async pingAndForget(serviceProvider: NodeDriverServiceProvider): Promise<void> {
2✔
297
        try {
14✔
298
            await serviceProvider?.runCommand?.("admin", { hello: 1 });
14✔
299
        } catch (error: unknown) {
14!
300
            this.logger.warning({
×
301
                id: LogId.oidcFlow,
×
302
                context: "pingAndForget",
×
303
                message: String(error),
×
304
            });
×
305
        }
×
306
    }
14✔
307

308
    private async disconnectOnOidcError(error: unknown): Promise<void> {
2✔
309
        try {
×
310
            await this.disconnect();
×
311
        } catch (error: unknown) {
×
312
            this.logger.warning({
×
313
                id: LogId.oidcFlow,
×
314
                context: "disconnectOnOidcError",
×
315
                message: String(error),
×
316
            });
×
317
        } finally {
×
318
            this.changeState("connection-errored", { tag: "errored", errorReason: String(error) });
×
319
        }
×
320
    }
×
321
}
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc