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

mongodb-js / mongodb-mcp-server / 14670404051

25 Apr 2025 05:45PM UTC coverage: 81.886% (+0.2%) from 81.696%
14670404051

Pull #118

github

nirinchev
Merge branch 'main' into ni/conditional-connect
Pull Request #118: feat: update the connect tool based on connectivity status

129 of 210 branches covered (61.43%)

Branch coverage included in aggregate %.

59 of 67 new or added lines in 9 files covered. (88.06%)

10 existing lines in 1 file now uncovered.

748 of 861 relevant lines covered (86.88%)

47.74 hits per line

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

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

10
const ATLAS_API_VERSION = "2025-03-12";
31✔
11

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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