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

mongodb-js / mongodb-mcp-server / 15030421235

14 May 2025 08:28PM UTC coverage: 74.11%. First build
15030421235

Pull #252

github

web-flow
Merge 741147a95 into fa6224105
Pull Request #252: feat: Add vector index create and update

207 of 363 branches covered (57.02%)

Branch coverage included in aggregate %.

13 of 22 new or added lines in 3 files covered. (59.09%)

792 of 985 relevant lines covered (80.41%)

52.86 hits per line

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

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

9
const ATLAS_API_VERSION = "2025-03-12";
39✔
10

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

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

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

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

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

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

58
    constructor(options: ApiClientOptions) {
59
        this.options = {
53✔
60
            ...options,
61
            userAgent:
62
                options.userAgent ||
98✔
63
                `AtlasMCP/${packageInfo.version} (${process.platform}; ${process.arch}; ${process.env.HOSTNAME || "unknown"})`,
90✔
64
        };
65

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

88
    public hasCredentials(): boolean {
89
        return !!(this.oauth2Client && this.accessToken);
3✔
90
    }
91

92
    public async validateAccessToken(): Promise<void> {
93
        await this.getAccessToken();
×
94
    }
95

96
    public async getIpInfo(): Promise<{
97
        currentIpv4Address: string;
98
    }> {
99
        const accessToken = await this.getAccessToken();
2✔
100

101
        const endpoint = "api/private/ipinfo";
2✔
102
        const url = new URL(endpoint, this.options.baseUrl);
2✔
103
        const response = await fetch(url, {
2✔
104
            method: "GET",
105
            headers: {
106
                Accept: "application/json",
107
                Authorization: `Bearer ${accessToken}`,
108
                "User-Agent": this.options.userAgent,
109
            },
110
        });
111

112
        if (!response.ok) {
2!
113
            throw await ApiClientError.fromResponse(response);
×
114
        }
115

116
        return (await response.json()) as Promise<{
2✔
117
            currentIpv4Address: string;
118
        }>;
119
    }
120

121
    public async sendEvents(events: TelemetryEvent<CommonProperties>[]): Promise<void> {
122
        if (!this.options.credentials) {
5!
123
            await this.sendUnauthEvents(events);
×
124
            return;
×
125
        }
126

127
        try {
5✔
128
            await this.sendAuthEvents(events);
5✔
129
        } catch (error) {
130
            if (error instanceof ApiClientError) {
4✔
131
                if (error.response.status !== 401) {
2!
132
                    throw error;
×
133
                }
134
            }
135

136
            // send unauth events if any of the following are true:
137
            // 1: the token is not valid (not ApiClientError)
138
            // 2: if the api responded with 401 (ApiClientError with status 401)
139
            await this.sendUnauthEvents(events);
4✔
140
        }
141
    }
142

143
    private async sendAuthEvents(events: TelemetryEvent<CommonProperties>[]): Promise<void> {
144
        const accessToken = await this.getAccessToken();
5✔
145
        if (!accessToken) {
4✔
146
            throw new Error("No access token available");
1✔
147
        }
148
        const authUrl = new URL("api/private/v1.0/telemetry/events", this.options.baseUrl);
3✔
149
        const response = await fetch(authUrl, {
3✔
150
            method: "POST",
151
            headers: {
152
                Accept: "application/json",
153
                "Content-Type": "application/json",
154
                "User-Agent": this.options.userAgent,
155
                Authorization: `Bearer ${accessToken}`,
156
            },
157
            body: JSON.stringify(events),
158
        });
159

160
        if (!response.ok) {
3✔
161
            throw await ApiClientError.fromResponse(response);
2✔
162
        }
163
    }
164

165
    private async sendUnauthEvents(events: TelemetryEvent<CommonProperties>[]): Promise<void> {
166
        const headers: Record<string, string> = {
4✔
167
            Accept: "application/json",
168
            "Content-Type": "application/json",
169
            "User-Agent": this.options.userAgent,
170
        };
171

172
        const unauthUrl = new URL("api/private/unauth/telemetry/events", this.options.baseUrl);
4✔
173
        const response = await fetch(unauthUrl, {
4✔
174
            method: "POST",
175
            headers,
176
            body: JSON.stringify(events),
177
        });
178

179
        if (!response.ok) {
4✔
180
            throw await ApiClientError.fromResponse(response);
1✔
181
        }
182
    }
183

184
    // DO NOT EDIT. This is auto-generated code.
185
    async listClustersForAllProjects(options?: FetchOptions<operations["listClustersForAllProjects"]>) {
186
        const { data, error, response } = await this.client.GET("/api/atlas/v2/clusters", options);
×
187
        if (error) {
×
188
            throw ApiClientError.fromError(response, error);
×
189
        }
190
        return data;
×
191
    }
192

193
    async listProjects(options?: FetchOptions<operations["listProjects"]>) {
194
        const { data, error, response } = await this.client.GET("/api/atlas/v2/groups", options);
4✔
195
        if (error) {
4✔
196
            throw ApiClientError.fromError(response, error);
1✔
197
        }
198
        return data;
3✔
199
    }
200

201
    async createProject(options: FetchOptions<operations["createProject"]>) {
202
        const { data, error, response } = await this.client.POST("/api/atlas/v2/groups", options);
5✔
203
        if (error) {
5!
204
            throw ApiClientError.fromError(response, error);
×
205
        }
206
        return data;
5✔
207
    }
208

209
    async deleteProject(options: FetchOptions<operations["deleteProject"]>) {
210
        const { error, response } = await this.client.DELETE("/api/atlas/v2/groups/{groupId}", options);
5✔
211
        if (error) {
5!
212
            throw ApiClientError.fromError(response, error);
×
213
        }
214
    }
215

216
    async getProject(options: FetchOptions<operations["getProject"]>) {
217
        const { data, error, response } = await this.client.GET("/api/atlas/v2/groups/{groupId}", options);
1✔
218
        if (error) {
1!
219
            throw ApiClientError.fromError(response, error);
×
220
        }
221
        return data;
1✔
222
    }
223

224
    async listProjectIpAccessLists(options: FetchOptions<operations["listProjectIpAccessLists"]>) {
225
        const { data, error, response } = await this.client.GET("/api/atlas/v2/groups/{groupId}/accessList", options);
1✔
226
        if (error) {
1!
227
            throw ApiClientError.fromError(response, error);
×
228
        }
229
        return data;
1✔
230
    }
231

232
    async createProjectIpAccessList(options: FetchOptions<operations["createProjectIpAccessList"]>) {
233
        const { data, error, response } = await this.client.POST("/api/atlas/v2/groups/{groupId}/accessList", options);
2✔
234
        if (error) {
2!
235
            throw ApiClientError.fromError(response, error);
×
236
        }
237
        return data;
2✔
238
    }
239

240
    async deleteProjectIpAccessList(options: FetchOptions<operations["deleteProjectIpAccessList"]>) {
241
        const { error, response } = await this.client.DELETE(
5✔
242
            "/api/atlas/v2/groups/{groupId}/accessList/{entryValue}",
243
            options
244
        );
245
        if (error) {
5!
246
            throw ApiClientError.fromError(response, error);
×
247
        }
248
    }
249

250
    async listAlerts(options: FetchOptions<operations["listAlerts"]>) {
251
        const { data, error, response } = await this.client.GET("/api/atlas/v2/groups/{groupId}/alerts", options);
1✔
252
        if (error) {
1!
253
            throw ApiClientError.fromError(response, error);
×
254
        }
255
        return data;
1✔
256
    }
257

258
    async listClusters(options: FetchOptions<operations["listClusters"]>) {
259
        const { data, error, response } = await this.client.GET("/api/atlas/v2/groups/{groupId}/clusters", options);
1✔
260
        if (error) {
1!
261
            throw ApiClientError.fromError(response, error);
×
262
        }
263
        return data;
1✔
264
    }
265

266
    async createCluster(options: FetchOptions<operations["createCluster"]>) {
267
        const { data, error, response } = await this.client.POST("/api/atlas/v2/groups/{groupId}/clusters", options);
1✔
268
        if (error) {
1!
269
            throw ApiClientError.fromError(response, error);
×
270
        }
271
        return data;
1✔
272
    }
273

274
    async deleteCluster(options: FetchOptions<operations["deleteCluster"]>) {
275
        const { error, response } = await this.client.DELETE(
1✔
276
            "/api/atlas/v2/groups/{groupId}/clusters/{clusterName}",
277
            options
278
        );
279
        if (error) {
1!
280
            throw ApiClientError.fromError(response, error);
×
281
        }
282
    }
283

284
    async getCluster(options: FetchOptions<operations["getCluster"]>) {
285
        const { data, error, response } = await this.client.GET(
45✔
286
            "/api/atlas/v2/groups/{groupId}/clusters/{clusterName}",
287
            options
288
        );
289

290
        if (error) {
45✔
291
            throw ApiClientError.fromError(response, error);
1✔
292
        }
293
        return data;
44✔
294
    }
295

296
    async listDatabaseUsers(options: FetchOptions<operations["listDatabaseUsers"]>) {
297
        const { data, error, response } = await this.client.GET(
1✔
298
            "/api/atlas/v2/groups/{groupId}/databaseUsers",
299
            options
300
        );
301
        if (error) {
1!
302
            throw ApiClientError.fromError(response, error);
×
303
        }
304
        return data;
1✔
305
    }
306

307
    async createDatabaseUser(options: FetchOptions<operations["createDatabaseUser"]>) {
308
        const { data, error, response } = await this.client.POST(
4✔
309
            "/api/atlas/v2/groups/{groupId}/databaseUsers",
310
            options
311
        );
312
        if (error) {
4!
313
            throw ApiClientError.fromError(response, error);
×
314
        }
315
        return data;
4✔
316
    }
317

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

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

336
    async createFlexCluster(options: FetchOptions<operations["createFlexCluster"]>) {
337
        const { data, error, response } = await this.client.POST(
×
338
            "/api/atlas/v2/groups/{groupId}/flexClusters",
339
            options
340
        );
341
        if (error) {
×
342
            throw ApiClientError.fromError(response, error);
×
343
        }
344
        return data;
×
345
    }
346

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

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

368
    async listOrganizations(options?: FetchOptions<operations["listOrganizations"]>) {
369
        const { data, error, response } = await this.client.GET("/api/atlas/v2/orgs", options);
7✔
370
        if (error) {
7!
371
            throw ApiClientError.fromError(response, error);
×
372
        }
373
        return data;
7✔
374
    }
375

376
    async listOrganizationProjects(options: FetchOptions<operations["listOrganizationProjects"]>) {
377
        const { data, error, response } = await this.client.GET("/api/atlas/v2/orgs/{orgId}/groups", options);
×
378
        if (error) {
×
379
            throw ApiClientError.fromError(response, error);
×
380
        }
381
        return data;
×
382
    }
383

384
    // DO NOT EDIT. This is auto-generated code.
385
}
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