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

mongodb-js / mongodb-mcp-server / 16497556937

24 Jul 2025 12:59PM UTC coverage: 66.991% (-13.3%) from 80.331%
16497556937

push

github

web-flow
chore(deps): bump @eslint/plugin-kit from 0.3.2 to 0.3.3 in the npm_and_yarn group (#384)

408 of 486 branches covered (83.95%)

Branch coverage included in aggregate %.

2480 of 3825 relevant lines covered (64.84%)

42.09 hits per line

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

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

10
const ATLAS_API_VERSION = "2025-03-12";
2✔
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 {
2✔
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 () => {
2✔
37
        if (this.oauth2Client && (!this.accessToken || this.accessToken.expired())) {
×
38
            this.accessToken = await this.oauth2Client.getToken({});
×
39
        }
×
40
        return this.accessToken?.token.access_token as string | undefined;
×
41
    };
×
42

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

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

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

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

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

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

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

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

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

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

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

142
    public async sendEvents(events: TelemetryEvent<CommonProperties>[]): Promise<void> {
2✔
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> {
2✔
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> {
2✔
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"]>) {
2✔
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"]>) {
2✔
215
        const { data, error, response } = await this.client.GET("/api/atlas/v2/groups", options);
4✔
216
        if (error) {
4✔
217
            throw ApiClientError.fromError(response, error);
2✔
218
        }
2✔
219
        return data;
2✔
220
    }
4✔
221

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

349
    async listFlexClusters(options: FetchOptions<operations["listFlexClusters"]>) {
2✔
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"]>) {
2✔
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"]>) {
2✔
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"]>) {
2✔
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"]>) {
2✔
390
        const { data, error, response } = await this.client.GET("/api/atlas/v2/orgs", options);
×
391
        if (error) {
×
392
            throw ApiClientError.fromError(response, error);
×
393
        }
×
394
        return data;
×
395
    }
×
396

397
    async listOrganizationProjects(options: FetchOptions<operations["listOrganizationProjects"]>) {
2✔
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
}
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