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

mongodb-js / mongodb-mcp-server / 14621147270

23 Apr 2025 02:47PM UTC coverage: 75.13% (-3.3%) from 78.42%
14621147270

Pull #87

github

blva
chore: reformat
Pull Request #87: feat: core telemetry functionality

100 of 188 branches covered (53.19%)

Branch coverage included in aggregate %.

51 of 90 new or added lines in 8 files covered. (56.67%)

16 existing lines in 4 files now uncovered.

622 of 773 relevant lines covered (80.47%)

37.2 hits per line

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

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

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

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

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

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

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

77
        this.client = createClient<paths>({
18✔
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) {
18✔
85
            this.oauth2Client = new ClientCredentials({
18✔
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);
18✔
96
        }
97
        this.client.use(this.errorMiddleware);
18✔
98
    }
99

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

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

114
        const endpoint = "api/private/ipinfo";
2✔
115
        const url = new URL(endpoint, this.options.baseUrl);
2✔
116
        const response = await fetch(url, {
2✔
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) {
2!
126
            throw await ApiClientError.fromResponse(response);
×
127
        }
128

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

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

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

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

NEW
155
        if (!response.ok) {
×
NEW
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);
2✔
168
        return data;
2✔
169
    }
170

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

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

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

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

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

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

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

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

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

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

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

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

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

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