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

mongodb-js / mongodb-mcp-server / 16292877152

15 Jul 2025 12:10PM UTC coverage: 77.385% (+2.1%) from 75.27%
16292877152

push

github

web-flow
chore(tests): switch to vitest (#363)

498 of 687 branches covered (72.49%)

Branch coverage included in aggregate %.

0 of 27 new or added lines in 2 files covered. (0.0%)

293 existing lines in 26 files now uncovered.

2828 of 3611 relevant lines covered (78.32%)

28.46 hits per line

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

91.85
/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,
47✔
29
        private readonly userConfig: UserConfig,
47✔
30
        private readonly commonProperties: CommonProperties,
47✔
31
        { eventCache, getRawMachineId }: { eventCache: EventCache; getRawMachineId: () => Promise<string> }
47✔
32
    ) {
47✔
33
        this.eventCache = eventCache;
47✔
34
        this.getRawMachineId = getRawMachineId;
47✔
35
    }
47✔
36

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

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

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

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

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

86
        this.isBufferingEvents = false;
13✔
87
    }
47✔
88

89
    public async close(): Promise<void> {
2✔
90
        this.deviceIdAbortController.abort();
34✔
91
        this.isBufferingEvents = false;
34✔
92
        await this.emitEvents(this.eventCache.getEvents());
34✔
93
    }
34✔
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 {
107✔
101
            if (!this.isTelemetryEnabled()) {
107✔
102
                logger.info(LogId.telemetryEmitFailure, "telemetry", `Telemetry is disabled.`);
104✔
103
                return;
104✔
104
            }
104!
105

106
            await this.emit(events);
3✔
107
        } catch {
5!
108
            logger.debug(LogId.telemetryEmitFailure, "telemetry", `Error emitting telemetry events.`);
×
UNCOV
109
        }
×
110
    }
107✔
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 {
17✔
118
            ...this.commonProperties,
17✔
119
            mcp_client_version: this.session.agentRunner?.version,
17✔
120
            mcp_client_name: this.session.agentRunner?.name,
17✔
121
            session_id: this.session.sessionId,
17✔
122
            config_atlas_auth: this.session.apiClient.hasCredentials() ? "true" : "false",
17✔
123
            config_connection_string: this.userConfig.connectionString ? "true" : "false",
17!
124
        };
17✔
125
    }
17✔
126

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

140
        const doNotTrack = "DO_NOT_TRACK" in process.env;
17✔
141
        return !doNotTrack;
17✔
142
    }
422✔
143

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

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

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

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

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

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