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

mongodb-js / mongodb-mcp-server / 16779406063

06 Aug 2025 02:11PM UTC coverage: 81.424% (-0.4%) from 81.781%
16779406063

Pull #420

github

web-flow
Merge cc01ac54e into a35d18dd6
Pull Request #420: chore: allow logging unredacted messages MCP-103

679 of 874 branches covered (77.69%)

Branch coverage included in aggregate %.

161 of 291 new or added lines in 16 files covered. (55.33%)

7 existing lines in 4 files now uncovered.

3485 of 4240 relevant lines covered (82.19%)

63.42 hits per line

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

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

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

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

56
    private async setup(): Promise<void> {
2✔
57
        if (!this.isTelemetryEnabled()) {
96✔
58
            return;
70✔
59
        }
70✔
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({
2✔
67
                                id: LogId.telemetryDeviceIdFailure,
2✔
68
                                context: "telemetry",
2✔
69
                                message: String(error),
2✔
70
                            });
2✔
71
                            break;
2✔
72
                        case "timeout":
4✔
73
                            logger.debug({
2✔
74
                                id: LogId.telemetryDeviceIdTimeout,
2✔
75
                                context: "telemetry",
2✔
76
                                message: "Device ID retrieval timed out",
2✔
77
                                noRedaction: true,
2✔
78
                            });
2✔
79
                            break;
2✔
80
                        case "abort":
4!
81
                            // No need to log in the case of aborts
82
                            break;
×
83
                    }
4✔
84
                },
4✔
85
                abortSignal: this.deviceIdAbortController.signal,
26✔
86
            }),
26✔
87
            detectContainerEnv(),
26✔
88
        ]);
26✔
89

90
        const [deviceId, containerEnv] = await this.setupPromise;
26✔
91

92
        this.commonProperties.device_id = deviceId;
26✔
93
        this.commonProperties.is_container_env = containerEnv;
26✔
94

95
        this.isBufferingEvents = false;
26✔
96
    }
96✔
97

98
    public async close(): Promise<void> {
2✔
99
        this.deviceIdAbortController.abort();
68✔
100
        this.isBufferingEvents = false;
68✔
101
        await this.emitEvents(this.eventCache.getEvents());
68✔
102
    }
68✔
103

104
    /**
105
     * Emits events through the telemetry pipeline
106
     * @param events - The events to emit
107
     */
108
    public async emitEvents(events: BaseEvent[]): Promise<void> {
2✔
109
        try {
214✔
110
            if (!this.isTelemetryEnabled()) {
214✔
111
                logger.info({
208✔
112
                    id: LogId.telemetryEmitFailure,
208✔
113
                    context: "telemetry",
208✔
114
                    message: "Telemetry is disabled.",
208✔
115
                    noRedaction: true,
208✔
116
                });
208✔
117
                return;
208✔
118
            }
208✔
119

120
            await this.emit(events);
6✔
121
        } catch {
10!
NEW
122
            logger.debug({
×
NEW
123
                id: LogId.telemetryEmitFailure,
×
NEW
124
                context: "telemetry",
×
NEW
125
                message: "Error emitting telemetry events.",
×
NEW
126
                noRedaction: true,
×
NEW
127
            });
×
UNCOV
128
        }
×
129
    }
214✔
130

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

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

160
        const doNotTrack = "DO_NOT_TRACK" in process.env;
34✔
161
        return !doNotTrack;
34✔
162
    }
832✔
163

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

174
        const cachedEvents = this.eventCache.getEvents();
6✔
175
        const allEvents = [...cachedEvents, ...events];
6✔
176

177
        logger.debug({
6✔
178
            id: LogId.telemetryEmitStart,
6✔
179
            context: "telemetry",
6✔
180
            message: `Attempting to send ${allEvents.length} events (${cachedEvents.length} cached)`,
6✔
181
        });
6✔
182

183
        const result = await this.sendEvents(this.session.apiClient, allEvents);
6✔
184
        if (result.success) {
6✔
185
            this.eventCache.clearEvents();
4✔
186
            logger.debug({
4✔
187
                id: LogId.telemetryEmitSuccess,
4✔
188
                context: "telemetry",
4✔
189
                message: `Sent ${allEvents.length} events successfully: ${JSON.stringify(allEvents, null, 2)}`,
4✔
190
            });
4✔
191
            return;
4✔
192
        }
4✔
193

194
        logger.debug({
2✔
195
            id: LogId.telemetryEmitFailure,
2✔
196
            context: "telemetry",
2✔
197
            message: `Error sending event to client: ${result.error instanceof Error ? result.error.message : String(result.error)}`,
6!
198
        });
6✔
199
        this.eventCache.appendEvents(events);
6✔
200
    }
6✔
201

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