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

mongodb-js / mongodb-mcp-server / 14760869443

30 Apr 2025 05:45PM UTC coverage: 70.911%. First build
14760869443

Pull #176

github

fmenezes
fix: compilation errors
Pull Request #176: fix: improve api error messages

157 of 308 branches covered (50.97%)

Branch coverage included in aggregate %.

43 of 72 new or added lines in 2 files covered. (59.72%)

645 of 823 relevant lines covered (78.37%)

37.4 hits per line

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

57.78
/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 "../../packageInfo.js";
8

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

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

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

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

66
        this.client = createClient<paths>({
33✔
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) {
33✔
74
            this.oauth2Client = new ClientCredentials({
7✔
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);
7✔
85
        }
86
    }
87

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

92
    public async getIpInfo(): Promise<{
93
        currentIpv4Address: string;
94
    }> {
95
        const accessToken = await this.getAccessToken();
2✔
96

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

108
        if (!response.ok) {
2!
109
            throw await ApiClientError.fromResponse(response);
×
110
        }
111

112
        return (await response.json()) as Promise<{
2✔
113
            currentIpv4Address: string;
114
        }>;
115
    }
116

117
    async sendEvents(events: TelemetryEvent<CommonProperties>[]): Promise<void> {
118
        let endpoint = "api/private/unauth/telemetry/events";
×
119
        const headers: Record<string, string> = {
×
120
            Accept: "application/json",
121
            "Content-Type": "application/json",
122
            "User-Agent": this.options.userAgent,
123
        };
124

125
        const accessToken = await this.getAccessToken();
×
126
        if (accessToken) {
×
127
            endpoint = "api/private/v1.0/telemetry/events";
×
128
            headers["Authorization"] = `Bearer ${accessToken}`;
×
129
        }
130

131
        const url = new URL(endpoint, this.options.baseUrl);
×
132
        const response = await fetch(url, {
×
133
            method: "POST",
134
            headers,
135
            body: JSON.stringify(events),
136
        });
137

138
        if (!response.ok) {
×
139
            throw await ApiClientError.fromResponse(response);
×
140
        }
141
    }
142

143
    // DO NOT EDIT. This is auto-generated code.
144
    async listClustersForAllProjects(options?: FetchOptions<operations["listClustersForAllProjects"]>) {
NEW
145
        const { data, error, response } = await this.client.GET("/api/atlas/v2/clusters", options);
×
NEW
146
        if (error) {
×
NEW
147
            throw ApiClientError.fromError(response, error);
×
148
        }
149
        return data;
×
150
    }
151

152
    async listProjects(options?: FetchOptions<operations["listProjects"]>) {
153
        const { data, error, response } = await this.client.GET("/api/atlas/v2/groups", options);
2✔
154
        if (error) {
2!
NEW
155
            throw ApiClientError.fromError(response, error);
×
156
        }
157
        return data;
2✔
158
    }
159

160
    async createProject(options: FetchOptions<operations["createProject"]>) {
161
        const { data, error, response } = await this.client.POST("/api/atlas/v2/groups", options);
4✔
162
        if (error) {
4!
NEW
163
            throw ApiClientError.fromError(response, error);
×
164
        }
165
        return data;
4✔
166
    }
167

168
    async deleteProject(options: FetchOptions<operations["deleteProject"]>) {
169
        const { error, response } = await this.client.DELETE("/api/atlas/v2/groups/{groupId}", options);
4✔
170
        if (error) {
4!
NEW
171
            throw ApiClientError.fromError(response, error);
×
172
        }
173
    }
174

175
    async getProject(options: FetchOptions<operations["getProject"]>) {
176
        const { data, error, response } = await this.client.GET("/api/atlas/v2/groups/{groupId}", options);
1✔
177
        if (error) {
1!
NEW
178
            throw ApiClientError.fromError(response, error);
×
179
        }
180
        return data;
1✔
181
    }
182

183
    async listProjectIpAccessLists(options: FetchOptions<operations["listProjectIpAccessLists"]>) {
184
        const { data, error, response } = await this.client.GET("/api/atlas/v2/groups/{groupId}/accessList", options);
1✔
185
        if (error) {
1!
NEW
186
            throw ApiClientError.fromError(response, error);
×
187
        }
188
        return data;
1✔
189
    }
190

191
    async createProjectIpAccessList(options: FetchOptions<operations["createProjectIpAccessList"]>) {
192
        const { data, error, response } = await this.client.POST("/api/atlas/v2/groups/{groupId}/accessList", options);
2✔
193
        if (error) {
2!
NEW
194
            throw ApiClientError.fromError(response, error);
×
195
        }
196
        return data;
2✔
197
    }
198

199
    async deleteProjectIpAccessList(options: FetchOptions<operations["deleteProjectIpAccessList"]>) {
200
        const { error, response } = await this.client.DELETE(
5✔
201
            "/api/atlas/v2/groups/{groupId}/accessList/{entryValue}",
202
            options
203
        );
204
        if (error) {
5!
NEW
205
            throw ApiClientError.fromError(response, error);
×
206
        }
207
    }
208

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

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

225
    async deleteCluster(options: FetchOptions<operations["deleteCluster"]>) {
226
        const { error, response } = await this.client.DELETE(
1✔
227
            "/api/atlas/v2/groups/{groupId}/clusters/{clusterName}",
228
            options
229
        );
230
        if (error) {
1!
NEW
231
            throw ApiClientError.fromError(response, error);
×
232
        }
233
    }
234

235
    async getCluster(options: FetchOptions<operations["getCluster"]>) {
236
        const { data, error, response } = await this.client.GET(
41✔
237
            "/api/atlas/v2/groups/{groupId}/clusters/{clusterName}",
238
            options
239
        );
240
        if (error) {
41✔
241
            throw ApiClientError.fromError(response, error);
1✔
242
        }
243
        return data;
40✔
244
    }
245

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

257
    async createDatabaseUser(options: FetchOptions<operations["createDatabaseUser"]>) {
258
        const { data, error, response } = await this.client.POST(
2✔
259
            "/api/atlas/v2/groups/{groupId}/databaseUsers",
260
            options
261
        );
262
        if (error) {
2!
NEW
263
            throw ApiClientError.fromError(response, error);
×
264
        }
265
        return data;
2✔
266
    }
267

268
    async deleteDatabaseUser(options: FetchOptions<operations["deleteDatabaseUser"]>) {
269
        const { error, response } = await this.client.DELETE(
2✔
270
            "/api/atlas/v2/groups/{groupId}/databaseUsers/{databaseName}/{username}",
271
            options
272
        );
273
        if (error) {
2!
NEW
274
            throw ApiClientError.fromError(response, error);
×
275
        }
276
    }
277

278
    async listOrganizations(options?: FetchOptions<operations["listOrganizations"]>) {
279
        const { data, error, response } = await this.client.GET("/api/atlas/v2/orgs", options);
6✔
280
        if (error) {
6!
NEW
281
            throw ApiClientError.fromError(response, error);
×
282
        }
283
        return data;
6✔
284
    }
285

286
    async listOrganizationProjects(options: FetchOptions<operations["listOrganizationProjects"]>) {
NEW
287
        const { data, error, response } = await this.client.GET("/api/atlas/v2/orgs/{orgId}/groups", options);
×
NEW
288
        if (error) {
×
NEW
289
            throw ApiClientError.fromError(response, error);
×
290
        }
291
        return data;
×
292
    }
293

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