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

mongodb-js / mongodb-mcp-server / 14671718729

25 Apr 2025 07:06PM UTC coverage: 82.257% (+0.6%) from 81.696%
14671718729

Pull #132

github

nirinchev
enable dependabot for gha
Pull Request #132: Add dependabot config

134 of 210 branches covered (63.81%)

Branch coverage included in aggregate %.

719 of 827 relevant lines covered (86.94%)

44.48 hits per line

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

79.17
/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 { mongoLogId } from "mongodb-log-writer";
31✔
8
import logger from "../../logger.js";
9
import { packageInfo } from "../../packageInfo.js";
31✔
10

11
const ATLAS_API_VERSION = "2025-03-12";
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
}
31✔
23

24
export class ApiClient {
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;
27✔
36

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

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

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

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

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

77
        this.client = createClient<paths>({
78
            baseUrl: this.options.baseUrl,
79
            headers: {
80
                "User-Agent": this.options.userAgent,
81
                Accept: `application/vnd.atlas.${ATLAS_API_VERSION}+json`,
82
            },
27✔
83
        });
6✔
84
        if (this.options.credentials?.clientId && this.options.credentials?.clientSecret) {
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
                },
6✔
94
            });
95
            this.client.use(this.authMiddleware);
27✔
96
        }
97
        this.client.use(this.errorMiddleware);
98
    }
99

283✔
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
        );
2✔
106
        return !!(this.oauth2Client && this.accessToken);
107
    }
2✔
108

2✔
109
    public async getIpInfo(): Promise<{
2✔
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: {
2!
119
                Accept: "application/json",
×
120
                Authorization: `Bearer ${accessToken}`,
121
                "User-Agent": this.options.userAgent,
122
            },
2✔
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.
2✔
161
    async listClustersForAllProjects(options?: FetchOptions<operations["listClustersForAllProjects"]>) {
2✔
162
        const { data } = await this.client.GET("/api/atlas/v2/clusters", options);
163
        return data;
164
    }
165

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

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

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

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

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

5✔
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
    }
1✔
194

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

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

1✔
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
    }
4✔
208

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

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

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

1✔
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
    }
6✔
227

6✔
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

© 2025 Coveralls, Inc