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

mongodb-js / mongodb-mcp-server / 16076959781

04 Jul 2025 03:28PM UTC coverage: 74.187% (-0.1%) from 74.286%
16076959781

Pull #339

github

web-flow
Merge 905959cd5 into d7d4aa9ae
Pull Request #339: chore: reinstate telemetry/docker change after revert MCP-49

233 of 394 branches covered (59.14%)

Branch coverage included in aggregate %.

20 of 24 new or added lines in 1 file covered. (83.33%)

816 of 1020 relevant lines covered (80.0%)

59.0 hits per line

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

84.44
/src/telemetry/telemetry.ts
1
import { Session } from "../session.js";
2
import { BaseEvent, CommonProperties } from "./types.js";
3
import { UserConfig } from "../config.js";
4
import logger, { LogId } from "../logger.js";
5
import { ApiClient } from "../common/atlas/apiClient.js";
6
import { MACHINE_METADATA } from "./constants.js";
7
import { EventCache } from "./eventCache.js";
8
import nodeMachineId from "node-machine-id";
9
import { getDeviceId } from "@mongodb-js/device-id";
10
import fs from "fs/promises";
11

12
type EventResult = {
13
    success: boolean;
14
    error?: Error;
15
};
16

17
export const DEVICE_ID_TIMEOUT = 3000;
36✔
18

19
export class Telemetry {
20
    private isBufferingEvents: boolean = true;
46✔
21
    /** Resolves when the setup is complete or a timeout occurs */
22
    public setupPromise: Promise<[string, boolean]> | undefined;
23
    private deviceIdAbortController = new AbortController();
46✔
24
    private eventCache: EventCache;
25
    private getRawMachineId: () => Promise<string>;
26

27
    private constructor(
28
        private readonly session: Session,
46✔
29
        private readonly userConfig: UserConfig,
46✔
30
        private readonly commonProperties: CommonProperties,
46✔
31
        { eventCache, getRawMachineId }: { eventCache: EventCache; getRawMachineId: () => Promise<string> }
32
    ) {
33
        this.eventCache = eventCache;
46✔
34
        this.getRawMachineId = getRawMachineId;
46✔
35
    }
36

37
    static create(
38
        session: Session,
39
        userConfig: UserConfig,
40
        {
34✔
41
            commonProperties = { ...MACHINE_METADATA },
46✔
42
            eventCache = EventCache.getInstance(),
37✔
43
            getRawMachineId = () => nodeMachineId.machineId(true),
1✔
44
        }: {
45
            eventCache?: EventCache;
46
            getRawMachineId?: () => Promise<string>;
47
            commonProperties?: CommonProperties;
48
        } = {}
49
    ): Telemetry {
50
        const instance = new Telemetry(session, userConfig, commonProperties, { eventCache, getRawMachineId });
46✔
51

52
        void instance.setup();
46✔
53
        return instance;
46✔
54
    }
55

56
    private async isContainerEnv(): Promise<boolean> {
57
        if (process.platform !== "linux") {
13!
NEW
58
            return false; // we only support linux containers for now
×
59
        }
60

61
        if (process.env.container) {
13!
NEW
62
            return true;
×
63
        }
64

65
        const exists = await Promise.all(
13✔
66
            ["/.dockerenv", "/run/.containerenv", "/var/run/.containerenv"].map(async (file) => {
67
                try {
39✔
68
                    await fs.access(file);
39✔
NEW
69
                    return true;
×
70
                } catch {
71
                    return false;
33✔
72
                }
73
            })
74
        );
75

76
        return exists.includes(true);
11✔
77
    }
78

79
    private async setup(): Promise<void> {
80
        if (!this.isTelemetryEnabled()) {
46✔
81
            return;
33✔
82
        }
83
        this.setupPromise = Promise.all([
13✔
84
            getDeviceId({
85
                getMachineId: () => this.getRawMachineId(),
13✔
86
                onError: (reason, error) => {
87
                    switch (reason) {
2!
88
                        case "resolutionError":
89
                            logger.debug(LogId.telemetryDeviceIdFailure, "telemetry", String(error));
1✔
90
                            break;
1✔
91
                        case "timeout":
92
                            logger.debug(LogId.telemetryDeviceIdTimeout, "telemetry", "Device ID retrieval timed out");
1✔
93
                            break;
1✔
94
                        case "abort":
95
                            // No need to log in the case of aborts
NEW
96
                            break;
×
97
                    }
98
                },
99
                abortSignal: this.deviceIdAbortController.signal,
100
            }),
101
            this.isContainerEnv(),
102
        ]);
103

104
        const [deviceId, containerEnv] = await this.setupPromise;
13✔
105

106
        this.commonProperties.device_id = deviceId;
11✔
107
        this.commonProperties.is_container_env = containerEnv;
11✔
108

109
        this.isBufferingEvents = false;
11✔
110
    }
111

112
    public async close(): Promise<void> {
113
        this.deviceIdAbortController.abort();
33✔
114
        this.isBufferingEvents = false;
33✔
115
        await this.emitEvents(this.eventCache.getEvents());
33✔
116
    }
117

118
    /**
119
     * Emits events through the telemetry pipeline
120
     * @param events - The events to emit
121
     */
122
    public async emitEvents(events: BaseEvent[]): Promise<void> {
123
        try {
104✔
124
            if (!this.isTelemetryEnabled()) {
104✔
125
                logger.info(LogId.telemetryEmitFailure, "telemetry", `Telemetry is disabled.`);
101✔
126
                return;
101✔
127
            }
128

129
            await this.emit(events);
3✔
130
        } catch {
131
            logger.debug(LogId.telemetryEmitFailure, "telemetry", `Error emitting telemetry events.`);
×
132
        }
133
    }
134

135
    /**
136
     * Gets the common properties for events
137
     * @returns Object containing common properties for all events
138
     */
139
    public getCommonProperties(): CommonProperties {
140
        return {
17✔
141
            ...this.commonProperties,
142
            mcp_client_version: this.session.agentRunner?.version,
143
            mcp_client_name: this.session.agentRunner?.name,
144
            session_id: this.session.sessionId,
145
            config_atlas_auth: this.session.apiClient.hasCredentials() ? "true" : "false",
17✔
146
            config_connection_string: this.userConfig.connectionString ? "true" : "false",
17!
147
        };
148
    }
149

150
    /**
151
     * Checks if telemetry is currently enabled
152
     * This is a method rather than a constant to capture runtime config changes
153
     *
154
     * Follows the Console Do Not Track standard (https://consoledonottrack.com/)
155
     * by respecting the DO_NOT_TRACK environment variable
156
     */
157
    public isTelemetryEnabled(): boolean {
158
        // Check if telemetry is explicitly disabled in config
159
        if (this.userConfig.telemetry === "disabled") {
416✔
160
            return false;
399✔
161
        }
162

163
        const doNotTrack = "DO_NOT_TRACK" in process.env;
17✔
164
        return !doNotTrack;
17✔
165
    }
166

167
    /**
168
     * Attempts to emit events through authenticated and unauthenticated clients
169
     * Falls back to caching if both attempts fail
170
     */
171
    private async emit(events: BaseEvent[]): Promise<void> {
172
        if (this.isBufferingEvents) {
3!
173
            this.eventCache.appendEvents(events);
×
174
            return;
×
175
        }
176

177
        const cachedEvents = this.eventCache.getEvents();
3✔
178
        const allEvents = [...cachedEvents, ...events];
3✔
179

180
        logger.debug(
3✔
181
            LogId.telemetryEmitStart,
182
            "telemetry",
183
            `Attempting to send ${allEvents.length} events (${cachedEvents.length} cached)`
184
        );
185

186
        const result = await this.sendEvents(this.session.apiClient, allEvents);
3✔
187
        if (result.success) {
3✔
188
            this.eventCache.clearEvents();
2✔
189
            logger.debug(
2✔
190
                LogId.telemetryEmitSuccess,
191
                "telemetry",
192
                `Sent ${allEvents.length} events successfully: ${JSON.stringify(allEvents, null, 2)}`
193
            );
194
            return;
2✔
195
        }
196

197
        logger.debug(
1✔
198
            LogId.telemetryEmitFailure,
199
            "telemetry",
200
            `Error sending event to client: ${result.error instanceof Error ? result.error.message : String(result.error)}`
1!
201
        );
202
        this.eventCache.appendEvents(events);
1✔
203
    }
204

205
    /**
206
     * Attempts to send events through the provided API client
207
     */
208
    private async sendEvents(client: ApiClient, events: BaseEvent[]): Promise<EventResult> {
209
        try {
3✔
210
            await client.sendEvents(
3✔
211
                events.map((event) => ({
4✔
212
                    ...event,
213
                    properties: { ...this.getCommonProperties(), ...event.properties },
214
                }))
215
            );
216
            return { success: true };
2✔
217
        } catch (error) {
218
            return {
1✔
219
                success: false,
220
                error: error instanceof Error ? error : new Error(String(error)),
1!
221
            };
222
        }
223
    }
224
}
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