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

mongodb-js / mongodb-mcp-server / 14599372114

22 Apr 2025 04:03PM UTC coverage: 57.889% (+4.7%) from 53.208%
14599372114

Pull #87

github

web-flow
Merge branch 'main' into telemetry
Pull Request #87: draft: telemetry

51 of 175 branches covered (29.14%)

Branch coverage included in aggregate %.

75 of 100 new or added lines in 7 files covered. (75.0%)

2 existing lines in 1 file now uncovered.

481 of 744 relevant lines covered (64.65%)

107.67 hits per line

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

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

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

13
export interface ApiClientOptions {
14
    credentials?: {
15
        clientId: string;
16
        clientSecret: string;
17
    };
18
    baseUrl?: string;
19
    userAgent?: string;
20
}
21

22
export class ApiClient {
11✔
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 () => {
109✔
36
        if (this.oauth2Client && (!this.accessToken || this.accessToken.expired())) {
141!
37
            this.accessToken = await this.oauth2Client.getToken({});
×
38
        }
39
        return this.accessToken?.token.access_token as string | undefined;
141✔
40
    };
41

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

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

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

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

75
        this.client = createClient<paths>({
109✔
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) {
109!
83
            this.oauth2Client = new ClientCredentials({
×
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);
×
94
        }
95
        this.client.use(this.errorMiddleware);
109✔
96
    }
97

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

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

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

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

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

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

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

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

153
        if (!response.ok) {
141✔
154
            throw await ApiClientError.fromResponse(response);
141✔
155
        }
156
    }
157

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

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

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

174
    async listClustersForAllProjects(options?: FetchOptions<operations["listClustersForAllProjects"]>) {
175
        const { data } = await this.client.GET("/api/atlas/v2/clusters", options);
×
176
        return data;
×
177
    }
178

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

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

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

194
    async listProjectIpAccessLists(options: FetchOptions<operations["listProjectIpAccessLists"]>) {
195
        const { data } = await this.client.GET("/api/atlas/v2/groups/{groupId}/accessList", options);
×
196
        return data;
×
197
    }
198

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

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

209
    async createDatabaseUser(options: FetchOptions<operations["createDatabaseUser"]>) {
210
        const { data } = await this.client.POST("/api/atlas/v2/groups/{groupId}/databaseUsers", options);
×
211
        return data;
×
212
    }
213

214
    async getCluster(options: FetchOptions<operations["getCluster"]>) {
215
        const { data } = await this.client.GET("/api/atlas/v2/groups/{groupId}/clusters/{clusterName}", options);
×
216
        return data;
×
217
    }
218
    // DO NOT EDIT. This is auto-generated code.
219
}
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