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

mongodb-js / mongodb-mcp-server / 14637601756

24 Apr 2025 08:57AM UTC coverage: 42.737% (-32.7%) from 75.474%
14637601756

Pull #103

github

fmenezes
fix: rename describes
Pull Request #103: refactor: split test helpers

30 of 187 branches covered (16.04%)

Branch coverage included in aggregate %.

376 of 763 relevant lines covered (49.28%)

1.85 hits per line

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

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

11
const ATLAS_API_VERSION = "2025-03-12";
1✔
12

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

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

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

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

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

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

60
    private readonly errorMiddleware: Middleware = {
2✔
61
        async onResponse({ response }) {
62
            if (!response.ok) {
×
63
                throw await ApiClientError.fromResponse(response);
×
64
            }
65
        },
66
    };
67

68
    constructor(options?: ApiClientOptions) {
69
        this.options = {
2✔
70
            ...options,
71
            baseUrl: options?.baseUrl || "https://cloud.mongodb.com/",
4✔
72
            userAgent:
73
                options?.userAgent ||
4✔
74
                `AtlasMCP/${packageInfo.version} (${process.platform}; ${process.arch}; ${process.env.HOSTNAME || "unknown"})`,
4✔
75
        };
76

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

100
    public hasCredentials(): boolean {
101
        logger.info(
×
102
            mongoLogId(1_000_000),
103
            "api-client",
104
            `Checking if API client has credentials: ${!!(this.oauth2Client && this.accessToken)}`
×
105
        );
106
        return !!(this.oauth2Client && this.accessToken);
×
107
    }
108

109
    public async getIpInfo(): Promise<{
110
        currentIpv4Address: string;
111
    }> {
112
        const accessToken = await this.getAccessToken();
×
113

114
        const endpoint = "api/private/ipinfo";
×
115
        const url = new URL(endpoint, this.options.baseUrl);
×
116
        const response = await fetch(url, {
×
117
            method: "GET",
118
            headers: {
119
                Accept: "application/json",
120
                Authorization: `Bearer ${accessToken}`,
121
                "User-Agent": this.options.userAgent,
122
            },
123
        });
124

125
        if (!response.ok) {
×
126
            throw await ApiClientError.fromResponse(response);
×
127
        }
128

129
        return (await response.json()) as Promise<{
×
130
            currentIpv4Address: string;
131
        }>;
132
    }
133

134
    async sendEvents(events: BaseEvent[]): Promise<void> {
135
        let endpoint = "api/private/unauth/telemetry/events";
×
136
        const headers: Record<string, string> = {
×
137
            Accept: "application/json",
138
            "Content-Type": "application/json",
139
            "User-Agent": this.options.userAgent,
140
        };
141

142
        const accessToken = await this.getAccessToken();
×
143
        if (accessToken) {
×
144
            endpoint = "api/private/v1.0/telemetry/events";
×
145
            headers["Authorization"] = `Bearer ${accessToken}`;
×
146
        }
147

148
        const url = new URL(endpoint, this.options.baseUrl);
×
149
        const response = await fetch(url, {
×
150
            method: "POST",
151
            headers,
152
            body: JSON.stringify(events),
153
        });
154

155
        if (!response.ok) {
×
156
            throw await ApiClientError.fromResponse(response);
×
157
        }
158
    }
159

160
    // DO NOT EDIT. This is auto-generated code.
161
    async listClustersForAllProjects(options?: FetchOptions<operations["listClustersForAllProjects"]>) {
162
        const { data } = await this.client.GET("/api/atlas/v2/clusters", options);
×
163
        return data;
×
164
    }
165

166
    async listProjects(options?: FetchOptions<operations["listProjects"]>) {
167
        const { data } = await this.client.GET("/api/atlas/v2/groups", options);
×
168
        return data;
×
169
    }
170

171
    async createProject(options: FetchOptions<operations["createProject"]>) {
172
        const { data } = await this.client.POST("/api/atlas/v2/groups", options);
×
173
        return data;
×
174
    }
175

176
    async deleteProject(options: FetchOptions<operations["deleteProject"]>) {
177
        await this.client.DELETE("/api/atlas/v2/groups/{groupId}", options);
×
178
    }
179

180
    async getProject(options: FetchOptions<operations["getProject"]>) {
181
        const { data } = await this.client.GET("/api/atlas/v2/groups/{groupId}", options);
×
182
        return data;
×
183
    }
184

185
    async listProjectIpAccessLists(options: FetchOptions<operations["listProjectIpAccessLists"]>) {
186
        const { data } = await this.client.GET("/api/atlas/v2/groups/{groupId}/accessList", options);
×
187
        return data;
×
188
    }
189

190
    async createProjectIpAccessList(options: FetchOptions<operations["createProjectIpAccessList"]>) {
191
        const { data } = await this.client.POST("/api/atlas/v2/groups/{groupId}/accessList", options);
×
192
        return data;
×
193
    }
194

195
    async deleteProjectIpAccessList(options: FetchOptions<operations["deleteProjectIpAccessList"]>) {
196
        await this.client.DELETE("/api/atlas/v2/groups/{groupId}/accessList/{entryValue}", options);
×
197
    }
198

199
    async listClusters(options: FetchOptions<operations["listClusters"]>) {
200
        const { data } = await this.client.GET("/api/atlas/v2/groups/{groupId}/clusters", options);
×
201
        return data;
×
202
    }
203

204
    async createCluster(options: FetchOptions<operations["createCluster"]>) {
205
        const { data } = await this.client.POST("/api/atlas/v2/groups/{groupId}/clusters", options);
×
206
        return data;
×
207
    }
208

209
    async deleteCluster(options: FetchOptions<operations["deleteCluster"]>) {
210
        await this.client.DELETE("/api/atlas/v2/groups/{groupId}/clusters/{clusterName}", options);
×
211
    }
212

213
    async getCluster(options: FetchOptions<operations["getCluster"]>) {
214
        const { data } = await this.client.GET("/api/atlas/v2/groups/{groupId}/clusters/{clusterName}", options);
×
215
        return data;
×
216
    }
217

218
    async listDatabaseUsers(options: FetchOptions<operations["listDatabaseUsers"]>) {
219
        const { data } = await this.client.GET("/api/atlas/v2/groups/{groupId}/databaseUsers", options);
×
220
        return data;
×
221
    }
222

223
    async createDatabaseUser(options: FetchOptions<operations["createDatabaseUser"]>) {
224
        const { data } = await this.client.POST("/api/atlas/v2/groups/{groupId}/databaseUsers", options);
×
225
        return data;
×
226
    }
227

228
    async deleteDatabaseUser(options: FetchOptions<operations["deleteDatabaseUser"]>) {
229
        await this.client.DELETE("/api/atlas/v2/groups/{groupId}/databaseUsers/{databaseName}/{username}", options);
×
230
    }
231

232
    async listOrganizations(options?: FetchOptions<operations["listOrganizations"]>) {
233
        const { data } = await this.client.GET("/api/atlas/v2/orgs", options);
×
234
        return data;
×
235
    }
236

237
    async listOrganizationProjects(options: FetchOptions<operations["listOrganizationProjects"]>) {
238
        const { data } = await this.client.GET("/api/atlas/v2/orgs/{orgId}/groups", options);
×
239
        return data;
×
240
    }
241

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