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

mongodb-js / mongodb-mcp-server / 14643452943

24 Apr 2025 01:55PM UTC coverage: 67.774% (-13.8%) from 81.603%
14643452943

Pull #115

github

blva
updatE
Pull Request #115: chore: add server events

88 of 202 branches covered (43.56%)

Branch coverage included in aggregate %.

13 of 17 new or added lines in 1 file covered. (76.47%)

126 existing lines in 13 files now uncovered.

585 of 791 relevant lines covered (73.96%)

37.5 hits per line

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

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

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

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

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

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

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

77
        this.client = createClient<paths>({
19✔
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) {
19!
UNCOV
85
            this.oauth2Client = new ClientCredentials({
×
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
            });
UNCOV
95
            this.client.use(this.authMiddleware);
×
96
        }
97
        this.client.use(this.errorMiddleware);
19✔
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
    }> {
UNCOV
112
        const accessToken = await this.getAccessToken();
×
113

UNCOV
114
        const endpoint = "api/private/ipinfo";
×
UNCOV
115
        const url = new URL(endpoint, this.options.baseUrl);
×
UNCOV
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

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

UNCOV
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";
19✔
136
        const headers: Record<string, string> = {
19✔
137
            Accept: "application/json",
138
            "Content-Type": "application/json",
139
            "User-Agent": this.options.userAgent,
140
        };
141

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

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

155
        if (!response.ok) {
19✔
156
            throw await ApiClientError.fromResponse(response);
19✔
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"]>) {
UNCOV
167
        const { data } = await this.client.GET("/api/atlas/v2/groups", options);
×
UNCOV
168
        return data;
×
169
    }
170

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

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

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

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

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

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

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

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

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

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

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

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

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

232
    async listOrganizations(options?: FetchOptions<operations["listOrganizations"]>) {
UNCOV
233
        const { data } = await this.client.GET("/api/atlas/v2/orgs", options);
×
UNCOV
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