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

mongodb-js / mongodb-mcp-server / 16297368178

15 Jul 2025 03:19PM UTC coverage: 77.209% (-0.4%) from 77.581%
16297368178

push

github

web-flow
chore(tests): use the newer `@vitest/eslint-plugin` package (#375)

519 of 724 branches covered (71.69%)

Branch coverage included in aggregate %.

0 of 1 new or added line in 1 file covered. (0.0%)

11 existing lines in 2 files now uncovered.

2828 of 3611 relevant lines covered (78.32%)

56.94 hits per line

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

72.39
/src/common/atlas/apiClient.ts
1
import createClient, { Client, Middleware } from "openapi-fetch";
4✔
2
import type { FetchOptions } from "openapi-fetch";
3
import { AccessToken, ClientCredentials } from "simple-oauth2";
4✔
4
import { ApiClientError } from "./apiClientError.js";
4✔
5
import { paths, operations } from "./openapi.js";
6
import { CommonProperties, TelemetryEvent } from "../../telemetry/types.js";
7
import { packageInfo } from "../packageInfo.js";
4✔
8
import logger, { LogId } from "../logger.js";
4✔
9

10
const ATLAS_API_VERSION = "2025-03-12";
4✔
11

12
export interface ApiClientCredentials {
13
    clientId: string;
14
    clientSecret: string;
15
}
16

17
export interface ApiClientOptions {
18
    credentials?: ApiClientCredentials;
19
    baseUrl: string;
20
    userAgent?: string;
21
}
22

23
export class ApiClient {
4✔
24
    private options: {
25
        baseUrl: string;
26
        userAgent: string;
27
        credentials?: {
28
            clientId: string;
29
            clientSecret: string;
30
        };
31
    };
32
    private client: Client<paths>;
33
    private oauth2Client?: ClientCredentials;
34
    private accessToken?: AccessToken;
35

36
    private getAccessToken = async () => {
4✔
37
        if (this.oauth2Client && (!this.accessToken || this.accessToken.expired())) {
167✔
38
            this.accessToken = await this.oauth2Client.getToken({});
12✔
39
        }
12✔
40
        return this.accessToken?.token.access_token as string | undefined;
167✔
41
    };
167✔
42

43
    private authMiddleware: Middleware = {
4✔
44
        onRequest: async ({ request, schemaPath }) => {
4✔
45
            if (schemaPath.startsWith("/api/private/unauth") || schemaPath.startsWith("/api/oauth")) {
163!
46
                return undefined;
×
47
            }
×
48

49
            try {
163✔
50
                const accessToken = await this.getAccessToken();
163✔
51
                if (accessToken) {
163✔
52
                    request.headers.set("Authorization", `Bearer ${accessToken}`);
163✔
53
                }
163✔
54
                return request;
163✔
55
            } catch {
163!
56
                // ignore not availble tokens, API will return 401
57
            }
×
58
        },
163✔
59
    };
4✔
60

61
    constructor(options: ApiClientOptions) {
4✔
62
        this.options = {
92✔
63
            ...options,
92✔
64
            userAgent:
92✔
65
                options.userAgent ||
92✔
66
                `AtlasMCP/${packageInfo.version} (${process.platform}; ${process.arch}; ${process.env.HOSTNAME || "unknown"})`,
76✔
67
        };
92✔
68

69
        this.client = createClient<paths>({
92✔
70
            baseUrl: this.options.baseUrl,
92✔
71
            headers: {
92✔
72
                "User-Agent": this.options.userAgent,
92✔
73
                Accept: `application/vnd.atlas.${ATLAS_API_VERSION}+json`,
92✔
74
            },
92✔
75
        });
92✔
76
        if (this.options.credentials?.clientId && this.options.credentials?.clientSecret) {
92✔
77
            this.oauth2Client = new ClientCredentials({
32✔
78
                client: {
32✔
79
                    id: this.options.credentials.clientId,
32✔
80
                    secret: this.options.credentials.clientSecret,
32✔
81
                },
32✔
82
                auth: {
32✔
83
                    tokenHost: this.options.baseUrl,
32✔
84
                    tokenPath: "/api/oauth/token",
32✔
85
                    revokePath: "/api/oauth/revoke",
32✔
86
                },
32✔
87
                http: {
32✔
88
                    headers: {
32✔
89
                        "User-Agent": this.options.userAgent,
32✔
90
                    },
32✔
91
                },
32✔
92
            });
32✔
93
            this.client.use(this.authMiddleware);
32✔
94
        }
32✔
95
    }
92✔
96

97
    public hasCredentials(): boolean {
4✔
98
        return !!this.oauth2Client;
6✔
99
    }
6✔
100

101
    public async validateAccessToken(): Promise<void> {
4✔
102
        await this.getAccessToken();
×
103
    }
×
104

105
    public async close(): Promise<void> {
4✔
106
        if (this.accessToken) {
68!
107
            try {
12✔
108
                await this.accessToken.revoke("access_token");
12✔
109
            } catch (error: unknown) {
12!
UNCOV
110
                const err = error instanceof Error ? error : new Error(String(error));
1!
UNCOV
111
                logger.error(LogId.atlasApiRevokeFailure, "apiClient", `Failed to revoke access token: ${err.message}`);
1✔
UNCOV
112
            }
1✔
113
            this.accessToken = undefined;
12✔
114
        }
12✔
115
    }
68✔
116

117
    public async getIpInfo(): Promise<{
4✔
118
        currentIpv4Address: string;
119
    }> {
4✔
120
        const accessToken = await this.getAccessToken();
4✔
121

122
        const endpoint = "api/private/ipinfo";
4✔
123
        const url = new URL(endpoint, this.options.baseUrl);
4✔
124
        const response = await fetch(url, {
4✔
125
            method: "GET",
4✔
126
            headers: {
4✔
127
                Accept: "application/json",
4✔
128
                Authorization: `Bearer ${accessToken}`,
4✔
129
                "User-Agent": this.options.userAgent,
4✔
130
            },
4✔
131
        });
4✔
132

133
        if (!response.ok) {
4!
134
            throw await ApiClientError.fromResponse(response);
×
135
        }
×
136

137
        return (await response.json()) as Promise<{
4✔
138
            currentIpv4Address: string;
139
        }>;
140
    }
4✔
141

142
    public async sendEvents(events: TelemetryEvent<CommonProperties>[]): Promise<void> {
4✔
143
        if (!this.options.credentials) {
10!
144
            await this.sendUnauthEvents(events);
×
145
            return;
×
146
        }
×
147

148
        try {
10✔
149
            await this.sendAuthEvents(events);
10✔
150
        } catch (error) {
10✔
151
            if (error instanceof ApiClientError) {
8✔
152
                if (error.response.status !== 401) {
4!
153
                    throw error;
×
154
                }
×
155
            }
4✔
156

157
            // send unauth events if any of the following are true:
158
            // 1: the token is not valid (not ApiClientError)
159
            // 2: if the api responded with 401 (ApiClientError with status 401)
160
            await this.sendUnauthEvents(events);
8✔
161
        }
6✔
162
    }
10✔
163

164
    private async sendAuthEvents(events: TelemetryEvent<CommonProperties>[]): Promise<void> {
4✔
165
        const accessToken = await this.getAccessToken();
10✔
166
        if (!accessToken) {
10✔
167
            throw new Error("No access token available");
2✔
168
        }
2✔
169
        const authUrl = new URL("api/private/v1.0/telemetry/events", this.options.baseUrl);
6✔
170
        const response = await fetch(authUrl, {
6✔
171
            method: "POST",
6✔
172
            headers: {
6✔
173
                Accept: "application/json",
6✔
174
                "Content-Type": "application/json",
6✔
175
                "User-Agent": this.options.userAgent,
6✔
176
                Authorization: `Bearer ${accessToken}`,
6✔
177
            },
6✔
178
            body: JSON.stringify(events),
6✔
179
        });
6✔
180

181
        if (!response.ok) {
10✔
182
            throw await ApiClientError.fromResponse(response);
4✔
183
        }
4✔
184
    }
10✔
185

186
    private async sendUnauthEvents(events: TelemetryEvent<CommonProperties>[]): Promise<void> {
4✔
187
        const headers: Record<string, string> = {
8✔
188
            Accept: "application/json",
8✔
189
            "Content-Type": "application/json",
8✔
190
            "User-Agent": this.options.userAgent,
8✔
191
        };
8✔
192

193
        const unauthUrl = new URL("api/private/unauth/telemetry/events", this.options.baseUrl);
8✔
194
        const response = await fetch(unauthUrl, {
8✔
195
            method: "POST",
8✔
196
            headers,
8✔
197
            body: JSON.stringify(events),
8✔
198
        });
8✔
199

200
        if (!response.ok) {
8✔
201
            throw await ApiClientError.fromResponse(response);
2✔
202
        }
2✔
203
    }
8✔
204

205
    // DO NOT EDIT. This is auto-generated code.
206
    async listClustersForAllProjects(options?: FetchOptions<operations["listClustersForAllProjects"]>) {
4✔
207
        const { data, error, response } = await this.client.GET("/api/atlas/v2/clusters", options);
×
208
        if (error) {
×
209
            throw ApiClientError.fromError(response, error);
×
210
        }
×
211
        return data;
×
212
    }
×
213

214
    async listProjects(options?: FetchOptions<operations["listProjects"]>) {
4✔
215
        const { data, error, response } = await this.client.GET("/api/atlas/v2/groups", options);
8✔
216
        if (error) {
8!
217
            throw ApiClientError.fromError(response, error);
2✔
218
        }
2✔
219
        return data;
6✔
220
    }
8✔
221

222
    async createProject(options: FetchOptions<operations["createProject"]>) {
4✔
223
        const { data, error, response } = await this.client.POST("/api/atlas/v2/groups", options);
10✔
224
        if (error) {
10!
225
            throw ApiClientError.fromError(response, error);
×
226
        }
×
227
        return data;
10✔
228
    }
10✔
229

230
    async deleteProject(options: FetchOptions<operations["deleteProject"]>) {
4✔
231
        const { error, response } = await this.client.DELETE("/api/atlas/v2/groups/{groupId}", options);
10✔
232
        if (error) {
10!
233
            throw ApiClientError.fromError(response, error);
×
234
        }
×
235
    }
10✔
236

237
    async getProject(options: FetchOptions<operations["getProject"]>) {
4✔
238
        const { data, error, response } = await this.client.GET("/api/atlas/v2/groups/{groupId}", options);
2✔
239
        if (error) {
2!
240
            throw ApiClientError.fromError(response, error);
×
241
        }
×
242
        return data;
2✔
243
    }
2✔
244

245
    async listProjectIpAccessLists(options: FetchOptions<operations["listProjectIpAccessLists"]>) {
4✔
246
        const { data, error, response } = await this.client.GET("/api/atlas/v2/groups/{groupId}/accessList", options);
2✔
247
        if (error) {
2!
248
            throw ApiClientError.fromError(response, error);
×
249
        }
×
250
        return data;
2✔
251
    }
2✔
252

253
    async createProjectIpAccessList(options: FetchOptions<operations["createProjectIpAccessList"]>) {
4✔
254
        const { data, error, response } = await this.client.POST("/api/atlas/v2/groups/{groupId}/accessList", options);
4✔
255
        if (error) {
4!
256
            throw ApiClientError.fromError(response, error);
×
257
        }
×
258
        return data;
4✔
259
    }
4✔
260

261
    async deleteProjectIpAccessList(options: FetchOptions<operations["deleteProjectIpAccessList"]>) {
4✔
262
        const { error, response } = await this.client.DELETE(
10✔
263
            "/api/atlas/v2/groups/{groupId}/accessList/{entryValue}",
10✔
264
            options
10✔
265
        );
10✔
266
        if (error) {
10!
267
            throw ApiClientError.fromError(response, error);
×
268
        }
×
269
    }
10✔
270

271
    async listAlerts(options: FetchOptions<operations["listAlerts"]>) {
4✔
272
        const { data, error, response } = await this.client.GET("/api/atlas/v2/groups/{groupId}/alerts", options);
2✔
273
        if (error) {
2!
274
            throw ApiClientError.fromError(response, error);
×
275
        }
×
276
        return data;
2✔
277
    }
2✔
278

279
    async listClusters(options: FetchOptions<operations["listClusters"]>) {
4✔
280
        const { data, error, response } = await this.client.GET("/api/atlas/v2/groups/{groupId}/clusters", options);
2✔
281
        if (error) {
2!
282
            throw ApiClientError.fromError(response, error);
×
283
        }
×
284
        return data;
2✔
285
    }
2✔
286

287
    async createCluster(options: FetchOptions<operations["createCluster"]>) {
4✔
288
        const { data, error, response } = await this.client.POST("/api/atlas/v2/groups/{groupId}/clusters", options);
2✔
289
        if (error) {
2!
290
            throw ApiClientError.fromError(response, error);
×
291
        }
×
292
        return data;
2✔
293
    }
2✔
294

295
    async deleteCluster(options: FetchOptions<operations["deleteCluster"]>) {
4✔
296
        const { error, response } = await this.client.DELETE(
2✔
297
            "/api/atlas/v2/groups/{groupId}/clusters/{clusterName}",
2✔
298
            options
2✔
299
        );
2✔
300
        if (error) {
2!
301
            throw ApiClientError.fromError(response, error);
×
302
        }
×
303
    }
2✔
304

305
    async getCluster(options: FetchOptions<operations["getCluster"]>) {
4✔
306
        const { data, error, response } = await this.client.GET(
77✔
307
            "/api/atlas/v2/groups/{groupId}/clusters/{clusterName}",
77✔
308
            options
77✔
309
        );
77✔
310

311
        if (error) {
77✔
312
            throw ApiClientError.fromError(response, error);
2✔
313
        }
2✔
314
        return data;
75✔
315
    }
77✔
316

317
    async listDatabaseUsers(options: FetchOptions<operations["listDatabaseUsers"]>) {
4✔
318
        const { data, error, response } = await this.client.GET(
2✔
319
            "/api/atlas/v2/groups/{groupId}/databaseUsers",
2✔
320
            options
2✔
321
        );
2✔
322
        if (error) {
2!
323
            throw ApiClientError.fromError(response, error);
×
324
        }
×
325
        return data;
2✔
326
    }
2✔
327

328
    async createDatabaseUser(options: FetchOptions<operations["createDatabaseUser"]>) {
4✔
329
        const { data, error, response } = await this.client.POST(
8✔
330
            "/api/atlas/v2/groups/{groupId}/databaseUsers",
8✔
331
            options
8✔
332
        );
8✔
333
        if (error) {
8!
334
            throw ApiClientError.fromError(response, error);
×
335
        }
×
336
        return data;
8✔
337
    }
8✔
338

339
    async deleteDatabaseUser(options: FetchOptions<operations["deleteDatabaseUser"]>) {
4✔
340
        const { error, response } = await this.client.DELETE(
12✔
341
            "/api/atlas/v2/groups/{groupId}/databaseUsers/{databaseName}/{username}",
12✔
342
            options
12✔
343
        );
12✔
344
        if (error) {
12✔
345
            throw ApiClientError.fromError(response, error);
4✔
346
        }
4✔
347
    }
12✔
348

349
    async listFlexClusters(options: FetchOptions<operations["listFlexClusters"]>) {
4✔
350
        const { data, error, response } = await this.client.GET("/api/atlas/v2/groups/{groupId}/flexClusters", options);
×
351
        if (error) {
×
352
            throw ApiClientError.fromError(response, error);
×
353
        }
×
354
        return data;
×
355
    }
×
356

357
    async createFlexCluster(options: FetchOptions<operations["createFlexCluster"]>) {
4✔
358
        const { data, error, response } = await this.client.POST(
×
359
            "/api/atlas/v2/groups/{groupId}/flexClusters",
×
360
            options
×
361
        );
×
362
        if (error) {
×
363
            throw ApiClientError.fromError(response, error);
×
364
        }
×
365
        return data;
×
366
    }
×
367

368
    async deleteFlexCluster(options: FetchOptions<operations["deleteFlexCluster"]>) {
4✔
369
        const { error, response } = await this.client.DELETE(
×
370
            "/api/atlas/v2/groups/{groupId}/flexClusters/{name}",
×
371
            options
×
372
        );
×
373
        if (error) {
×
374
            throw ApiClientError.fromError(response, error);
×
375
        }
×
376
    }
×
377

378
    async getFlexCluster(options: FetchOptions<operations["getFlexCluster"]>) {
4✔
379
        const { data, error, response } = await this.client.GET(
×
380
            "/api/atlas/v2/groups/{groupId}/flexClusters/{name}",
×
381
            options
×
382
        );
×
383
        if (error) {
×
384
            throw ApiClientError.fromError(response, error);
×
385
        }
×
386
        return data;
×
387
    }
×
388

389
    async listOrganizations(options?: FetchOptions<operations["listOrganizations"]>) {
4✔
390
        const { data, error, response } = await this.client.GET("/api/atlas/v2/orgs", options);
14✔
391
        if (error) {
14!
392
            throw ApiClientError.fromError(response, error);
×
393
        }
×
394
        return data;
14✔
395
    }
14✔
396

397
    async listOrganizationProjects(options: FetchOptions<operations["listOrganizationProjects"]>) {
4✔
398
        const { data, error, response } = await this.client.GET("/api/atlas/v2/orgs/{orgId}/groups", options);
×
399
        if (error) {
×
400
            throw ApiClientError.fromError(response, error);
×
401
        }
×
402
        return data;
×
403
    }
×
404

405
    // DO NOT EDIT. This is auto-generated code.
406
}
4✔
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