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

mongodb-js / mongodb-mcp-server / 16448811352

22 Jul 2025 03:31PM UTC coverage: 80.318% (-1.5%) from 81.82%
16448811352

Pull #387

github

web-flow
Merge 5a799519a into f8e500004
Pull Request #387: chore: update JIRA automation

538 of 706 branches covered (76.2%)

Branch coverage included in aggregate %.

3102 of 3826 relevant lines covered (81.08%)

47.32 hits per line

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

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

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

17
export const DEVICE_ID_TIMEOUT = 3000;
2✔
18

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

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

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

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

56
    private async setup(): Promise<void> {
2✔
57
        if (!this.isTelemetryEnabled()) {
90✔
58
            return;
64✔
59
        }
64✔
60
        this.setupPromise = Promise.all([
26✔
61
            getDeviceId({
26✔
62
                getMachineId: () => this.getRawMachineId(),
26✔
63
                onError: (reason, error) => {
26✔
64
                    switch (reason) {
4✔
65
                        case "resolutionError":
4✔
66
                            logger.debug(LogId.telemetryDeviceIdFailure, "telemetry", String(error));
2✔
67
                            break;
2✔
68
                        case "timeout":
4✔
69
                            logger.debug(LogId.telemetryDeviceIdTimeout, "telemetry", "Device ID retrieval timed out");
2✔
70
                            break;
2✔
71
                        case "abort":
4!
72
                            // No need to log in the case of aborts
73
                            break;
×
74
                    }
4✔
75
                },
4✔
76
                abortSignal: this.deviceIdAbortController.signal,
26✔
77
            }),
26✔
78
            detectContainerEnv(),
26✔
79
        ]);
26✔
80

81
        const [deviceId, containerEnv] = await this.setupPromise;
26✔
82

83
        this.commonProperties.device_id = deviceId;
26✔
84
        this.commonProperties.is_container_env = containerEnv;
26✔
85

86
        this.isBufferingEvents = false;
26✔
87
    }
90✔
88

89
    public async close(): Promise<void> {
2✔
90
        this.deviceIdAbortController.abort();
64✔
91
        this.isBufferingEvents = false;
64✔
92
        await this.emitEvents(this.eventCache.getEvents());
64✔
93
    }
64✔
94

95
    /**
96
     * Emits events through the telemetry pipeline
97
     * @param events - The events to emit
98
     */
99
    public async emitEvents(events: BaseEvent[]): Promise<void> {
2✔
100
        try {
202✔
101
            if (!this.isTelemetryEnabled()) {
202✔
102
                logger.info(LogId.telemetryEmitFailure, "telemetry", `Telemetry is disabled.`);
196✔
103
                return;
196✔
104
            }
196✔
105

106
            await this.emit(events);
6✔
107
        } catch {
10!
108
            logger.debug(LogId.telemetryEmitFailure, "telemetry", `Error emitting telemetry events.`);
×
109
        }
×
110
    }
202✔
111

112
    /**
113
     * Gets the common properties for events
114
     * @returns Object containing common properties for all events
115
     */
116
    public getCommonProperties(): CommonProperties {
2✔
117
        return {
34✔
118
            ...this.commonProperties,
34✔
119
            transport: this.userConfig.transport,
34✔
120
            mcp_client_version: this.session.agentRunner?.version,
34✔
121
            mcp_client_name: this.session.agentRunner?.name,
34✔
122
            session_id: this.session.sessionId,
34✔
123
            config_atlas_auth: this.session.apiClient.hasCredentials() ? "true" : "false",
34✔
124
            config_connection_string: this.userConfig.connectionString ? "true" : "false",
34!
125
        };
34✔
126
    }
34✔
127

128
    /**
129
     * Checks if telemetry is currently enabled
130
     * This is a method rather than a constant to capture runtime config changes
131
     *
132
     * Follows the Console Do Not Track standard (https://consoledonottrack.com/)
133
     * by respecting the DO_NOT_TRACK environment variable
134
     */
135
    public isTelemetryEnabled(): boolean {
2✔
136
        // Check if telemetry is explicitly disabled in config
137
        if (this.userConfig.telemetry === "disabled") {
814✔
138
            return false;
780✔
139
        }
780✔
140

141
        const doNotTrack = "DO_NOT_TRACK" in process.env;
34✔
142
        return !doNotTrack;
34✔
143
    }
814✔
144

145
    /**
146
     * Attempts to emit events through authenticated and unauthenticated clients
147
     * Falls back to caching if both attempts fail
148
     */
149
    private async emit(events: BaseEvent[]): Promise<void> {
2✔
150
        if (this.isBufferingEvents) {
6!
151
            this.eventCache.appendEvents(events);
×
152
            return;
×
153
        }
×
154

155
        const cachedEvents = this.eventCache.getEvents();
6✔
156
        const allEvents = [...cachedEvents, ...events];
6✔
157

158
        logger.debug(
6✔
159
            LogId.telemetryEmitStart,
6✔
160
            "telemetry",
6✔
161
            `Attempting to send ${allEvents.length} events (${cachedEvents.length} cached)`
6✔
162
        );
6✔
163

164
        const result = await this.sendEvents(this.session.apiClient, allEvents);
6✔
165
        if (result.success) {
6✔
166
            this.eventCache.clearEvents();
4✔
167
            logger.debug(
4✔
168
                LogId.telemetryEmitSuccess,
4✔
169
                "telemetry",
4✔
170
                `Sent ${allEvents.length} events successfully: ${JSON.stringify(allEvents, null, 2)}`
4✔
171
            );
4✔
172
            return;
4✔
173
        }
4✔
174

175
        logger.debug(
2✔
176
            LogId.telemetryEmitFailure,
2✔
177
            "telemetry",
2✔
178
            `Error sending event to client: ${result.error instanceof Error ? result.error.message : String(result.error)}`
6!
179
        );
6✔
180
        this.eventCache.appendEvents(events);
6✔
181
    }
6✔
182

183
    /**
184
     * Attempts to send events through the provided API client
185
     */
186
    private async sendEvents(client: ApiClient, events: BaseEvent[]): Promise<EventResult> {
2✔
187
        try {
6✔
188
            await client.sendEvents(
6✔
189
                events.map((event) => ({
6✔
190
                    ...event,
8✔
191
                    properties: { ...this.getCommonProperties(), ...event.properties },
8✔
192
                }))
6✔
193
            );
6✔
194
            return { success: true };
4✔
195
        } catch (error) {
6✔
196
            return {
2✔
197
                success: false,
2✔
198
                error: error instanceof Error ? error : new Error(String(error)),
2!
199
            };
2✔
200
        }
2✔
201
    }
6✔
202
}
2✔
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