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

mongodb-js / mongodb-mcp-server / 17155288437

22 Aug 2025 12:29PM UTC coverage: 82.014% (-0.03%) from 82.04%
17155288437

Pull #468

github

web-flow
Merge 3bb127c20 into ca68195c4
Pull Request #468: chore: update readme with the new tool and resources

855 of 1075 branches covered (79.53%)

Branch coverage included in aggregate %.

2 of 2 new or added lines in 2 files covered. (100.0%)

97 existing lines in 8 files now uncovered.

4357 of 5280 relevant lines covered (82.52%)

70.4 hits per line

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

90.56
/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,
96✔
80
        private driverOptions: DriverOptions,
96✔
81
        private logger: CompositeLogger,
96✔
82
        deviceId: DeviceId,
96✔
83
        bus?: EventEmitter
96✔
84
    ) {
96✔
85
        super();
96✔
86

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

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

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

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

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

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

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

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

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

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

130
            if (connectionInfo.driverOptions.oidc) {
435✔
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 };
435✔
136
            connectionInfo.driverOptions.applyProxyToOIDC ??= true;
435✔
137

138
            serviceProvider = await NodeDriverServiceProvider.connect(
435✔
139
                connectionInfo.connectionString,
435✔
140
                {
435✔
141
                    productDocsLink: "https://github.com/mongodb-js/mongodb-mcp-server/",
435✔
142
                    productName: "MongoDB MCP",
435✔
143
                    ...connectionInfo.driverOptions,
435✔
144
                },
435✔
145
                undefined,
435✔
146
                this.bus
435✔
147
            );
435✔
148
        } catch (error: unknown) {
435✔
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 {
425✔
159
            const connectionType = ConnectionManager.inferConnectionTypeFromSettings(this.userConfig, connectionInfo);
425✔
160
            if (connectionType.startsWith("oidc")) {
435✔
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 });
411✔
173

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

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

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

206
        return { tag: "disconnected" };
387✔
207
    }
852✔
208

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

213
    changeState<Event extends keyof ConnectionManagerEvents, State extends ConnectionManagerEvents[Event][0]>(
2✔
214
        event: Event,
878✔
215
        newState: State
878✔
216
    ): State {
878✔
217
        this.state = newState;
878✔
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));
878✔
221
        return newState;
878✔
222
    }
878✔
223

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

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

235
        this.logger.info({
19✔
236
            id: LogId.oidcFlow,
19✔
237
            context: "mongodb-oidc-plugin:auth-succeeded",
19✔
238
            message: "Authenticated successfully.",
19✔
239
        });
19✔
240
    }
19✔
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,
443✔
262
        settings: { connectionString: string }
443✔
263
    ): ConnectionStringAuthType {
443✔
264
        const connString = new ConnectionString(settings.connectionString);
443✔
265
        const searchParams = connString.typedSearchParams<MongoClientOptions>();
443✔
266

267
        switch (searchParams.get("authMechanism")) {
443✔
268
            case "MONGODB-OIDC": {
443✔
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":
443✔
280
                return "x.509";
2✔
281
            case "GSSAPI":
443✔
282
                return "kerberos";
2✔
283
            case "PLAIN":
443✔
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:
443✔
291
            default:
443✔
292
                return "scram";
413✔
293
        }
443✔
294
    }
443✔
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",
×
UNCOV
303
                message: String(error),
×
UNCOV
304
            });
×
UNCOV
305
        }
×
306
    }
14✔
307

308
    private async disconnectOnOidcError(error: unknown): Promise<void> {
2✔
UNCOV
309
        try {
×
UNCOV
310
            await this.disconnect();
×
UNCOV
311
        } catch (error: unknown) {
×
UNCOV
312
            this.logger.warning({
×
UNCOV
313
                id: LogId.oidcFlow,
×
UNCOV
314
                context: "disconnectOnOidcError",
×
UNCOV
315
                message: String(error),
×
UNCOV
316
            });
×
UNCOV
317
        } finally {
×
UNCOV
318
            this.changeState("connection-errored", { tag: "errored", errorReason: String(error) });
×
UNCOV
319
        }
×
UNCOV
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

© 2025 Coveralls, Inc