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

mongodb-js / mongodb-mcp-server / 14672998392

25 Apr 2025 08:31PM UTC coverage: 82.446%. Remained the same
14672998392

Pull #137

github

nirinchev
Update name for fork workflow
Pull Request #137: chore: revamp gha workflows

135 of 212 branches covered (63.68%)

Branch coverage included in aggregate %.

748 of 859 relevant lines covered (87.08%)

48.14 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 { packageInfo } from "../../packageInfo.js";
31✔
8

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

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

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

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

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

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

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

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

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

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

98
    public hasCredentials(): boolean {
99
        return !!(this.oauth2Client && this.accessToken);
282✔
100
    }
101

102
    public async getIpInfo(): Promise<{
103
        currentIpv4Address: string;
104
    }> {
105
        const accessToken = await this.getAccessToken();
2✔
106

107
        const endpoint = "api/private/ipinfo";
2✔
108
        const url = new URL(endpoint, this.options.baseUrl);
2✔
109
        const response = await fetch(url, {
2✔
110
            method: "GET",
111
            headers: {
112
                Accept: "application/json",
113
                Authorization: `Bearer ${accessToken}`,
114
                "User-Agent": this.options.userAgent,
115
            },
116
        });
117

118
        if (!response.ok) {
2!
119
            throw await ApiClientError.fromResponse(response);
×
120
        }
121

122
        return (await response.json()) as Promise<{
2✔
123
            currentIpv4Address: string;
124
        }>;
125
    }
126

127
    async sendEvents(events: BaseEvent[]): Promise<void> {
128
        let endpoint = "api/private/unauth/telemetry/events";
×
129
        const headers: Record<string, string> = {
×
130
            Accept: "application/json",
131
            "Content-Type": "application/json",
132
            "User-Agent": this.options.userAgent,
133
        };
134

135
        const accessToken = await this.getAccessToken();
×
136
        if (accessToken) {
×
137
            endpoint = "api/private/v1.0/telemetry/events";
×
138
            headers["Authorization"] = `Bearer ${accessToken}`;
×
139
        }
140

141
        const url = new URL(endpoint, this.options.baseUrl);
×
142
        const response = await fetch(url, {
×
143
            method: "POST",
144
            headers,
145
            body: JSON.stringify(events),
146
        });
147

148
        if (!response.ok) {
×
149
            throw await ApiClientError.fromResponse(response);
×
150
        }
151
    }
152

153
    // DO NOT EDIT. This is auto-generated code.
154
    async listClustersForAllProjects(options?: FetchOptions<operations["listClustersForAllProjects"]>) {
155
        const { data } = await this.client.GET("/api/atlas/v2/clusters", options);
×
156
        return data;
×
157
    }
158

159
    async listProjects(options?: FetchOptions<operations["listProjects"]>) {
160
        const { data } = await this.client.GET("/api/atlas/v2/groups", options);
2✔
161
        return data;
2✔
162
    }
163

164
    async createProject(options: FetchOptions<operations["createProject"]>) {
165
        const { data } = await this.client.POST("/api/atlas/v2/groups", options);
4✔
166
        return data;
4✔
167
    }
168

169
    async deleteProject(options: FetchOptions<operations["deleteProject"]>) {
170
        await this.client.DELETE("/api/atlas/v2/groups/{groupId}", options);
4✔
171
    }
172

173
    async getProject(options: FetchOptions<operations["getProject"]>) {
174
        const { data } = await this.client.GET("/api/atlas/v2/groups/{groupId}", options);
1✔
175
        return data;
1✔
176
    }
177

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

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

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

192
    async listClusters(options: FetchOptions<operations["listClusters"]>) {
193
        const { data } = await this.client.GET("/api/atlas/v2/groups/{groupId}/clusters", options);
1✔
194
        return data;
1✔
195
    }
196

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

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

206
    async getCluster(options: FetchOptions<operations["getCluster"]>) {
207
        const { data } = await this.client.GET("/api/atlas/v2/groups/{groupId}/clusters/{clusterName}", options);
4✔
208
        return data;
3✔
209
    }
210

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

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

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

225
    async listOrganizations(options?: FetchOptions<operations["listOrganizations"]>) {
226
        const { data } = await this.client.GET("/api/atlas/v2/orgs", options);
6✔
227
        return data;
6✔
228
    }
229

230
    async listOrganizationProjects(options: FetchOptions<operations["listOrganizationProjects"]>) {
231
        const { data } = await this.client.GET("/api/atlas/v2/orgs/{orgId}/groups", options);
×
232
        return data;
×
233
    }
234

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