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

mongodb-js / mongodb-mcp-server / 15979273150

30 Jun 2025 05:11PM UTC coverage: 75.857% (+0.9%) from 74.965%
15979273150

Pull #330

github

web-flow
Merge df8668ab0 into 53ea41c00
Pull Request #330: revert: rollback "chore: add is_container_env to telemetry MCP-2

235 of 392 branches covered (59.95%)

Branch coverage included in aggregate %.

41 of 45 new or added lines in 3 files covered. (91.11%)

1 existing line in 1 file now uncovered.

827 of 1008 relevant lines covered (82.04%)

59.8 hits per line

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

88.16
/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

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

16
export const DEVICE_ID_TIMEOUT = 3000;
36✔
17

18
export class Telemetry {
19
    private isBufferingEvents: boolean = true;
46✔
20
    /** Resolves when the device ID is retrieved or timeout occurs */
21
    public deviceIdPromise: Promise<string> | undefined;
22
    private deviceIdAbortController = new AbortController();
46✔
23
    private eventCache: EventCache;
24
    private getRawMachineId: () => Promise<string>;
25

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

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

51
        void instance.start();
46✔
52
        return instance;
46✔
53
    }
54

55
    private async start(): Promise<void> {
56
        if (!this.isTelemetryEnabled()) {
46✔
57
            return;
33✔
58
        }
59
        this.deviceIdPromise = getDeviceId({
13✔
60
            getMachineId: () => this.getRawMachineId(),
13✔
61
            onError: (reason, error) => {
62
                switch (reason) {
2!
63
                    case "resolutionError":
64
                        logger.debug(LogId.telemetryDeviceIdFailure, "telemetry", String(error));
1✔
65
                        break;
1✔
66
                    case "timeout":
67
                        logger.debug(LogId.telemetryDeviceIdTimeout, "telemetry", "Device ID retrieval timed out");
1✔
68
                        break;
1✔
69
                    case "abort":
70
                        // No need to log in the case of aborts
NEW
71
                        break;
×
72
                }
73
            },
74
            abortSignal: this.deviceIdAbortController.signal,
75
        });
76

77
        this.commonProperties.device_id = await this.deviceIdPromise;
13✔
78

79
        this.isBufferingEvents = false;
13✔
80
    }
81

82
    public async close(): Promise<void> {
83
        this.deviceIdAbortController.abort();
33✔
84
        this.isBufferingEvents = false;
33✔
85
        await this.emitEvents(this.eventCache.getEvents());
33✔
86
    }
87

88
    /**
89
     * Emits events through the telemetry pipeline
90
     * @param events - The events to emit
91
     */
92
    public async emitEvents(events: BaseEvent[]): Promise<void> {
93
        try {
104✔
94
            if (!this.isTelemetryEnabled()) {
104✔
95
                logger.info(LogId.telemetryEmitFailure, "telemetry", `Telemetry is disabled.`);
101✔
96
                return;
101✔
97
            }
98

99
            await this.emit(events);
3✔
100
        } catch {
NEW
101
            logger.debug(LogId.telemetryEmitFailure, "telemetry", `Error emitting telemetry events.`);
×
102
        }
103
    }
104

105
    /**
106
     * Gets the common properties for events
107
     * @returns Object containing common properties for all events
108
     */
109
    public getCommonProperties(): CommonProperties {
110
        return {
17✔
111
            ...this.commonProperties,
112
            mcp_client_version: this.session.agentRunner?.version,
113
            mcp_client_name: this.session.agentRunner?.name,
114
            session_id: this.session.sessionId,
115
            config_atlas_auth: this.session.apiClient.hasCredentials() ? "true" : "false",
17✔
116
            config_connection_string: this.userConfig.connectionString ? "true" : "false",
17!
117
        };
118
    }
119

120
    /**
121
     * Checks if telemetry is currently enabled
122
     * This is a method rather than a constant to capture runtime config changes
123
     *
124
     * Follows the Console Do Not Track standard (https://consoledonottrack.com/)
125
     * by respecting the DO_NOT_TRACK environment variable
126
     */
127
    public isTelemetryEnabled(): boolean {
128
        // Check if telemetry is explicitly disabled in config
129
        if (this.userConfig.telemetry === "disabled") {
416✔
130
            return false;
399✔
131
        }
132

133
        const doNotTrack = "DO_NOT_TRACK" in process.env;
17✔
134
        return !doNotTrack;
17✔
135
    }
136

137
    /**
138
     * Attempts to emit events through authenticated and unauthenticated clients
139
     * Falls back to caching if both attempts fail
140
     */
141
    private async emit(events: BaseEvent[]): Promise<void> {
142
        if (this.isBufferingEvents) {
3!
NEW
143
            this.eventCache.appendEvents(events);
×
UNCOV
144
            return;
×
145
        }
146

147
        const cachedEvents = this.eventCache.getEvents();
3✔
148
        const allEvents = [...cachedEvents, ...events];
3✔
149

150
        logger.debug(
3✔
151
            LogId.telemetryEmitStart,
152
            "telemetry",
153
            `Attempting to send ${allEvents.length} events (${cachedEvents.length} cached)`
154
        );
155

156
        const result = await this.sendEvents(this.session.apiClient, allEvents);
3✔
157
        if (result.success) {
3✔
158
            this.eventCache.clearEvents();
2✔
159
            logger.debug(
2✔
160
                LogId.telemetryEmitSuccess,
161
                "telemetry",
162
                `Sent ${allEvents.length} events successfully: ${JSON.stringify(allEvents, null, 2)}`
163
            );
164
            return;
2✔
165
        }
166

167
        logger.debug(
1✔
168
            LogId.telemetryEmitFailure,
169
            "telemetry",
170
            `Error sending event to client: ${result.error instanceof Error ? result.error.message : String(result.error)}`
1!
171
        );
172
        this.eventCache.appendEvents(events);
1✔
173
    }
174

175
    /**
176
     * Attempts to send events through the provided API client
177
     */
178
    private async sendEvents(client: ApiClient, events: BaseEvent[]): Promise<EventResult> {
179
        try {
3✔
180
            await client.sendEvents(
3✔
181
                events.map((event) => ({
4✔
182
                    ...event,
183
                    properties: { ...this.getCommonProperties(), ...event.properties },
184
                }))
185
            );
186
            return { success: true };
2✔
187
        } catch (error) {
188
            return {
1✔
189
                success: false,
190
                error: error instanceof Error ? error : new Error(String(error)),
1!
191
            };
192
        }
193
    }
194
}
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