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

mongodb-js / mongodb-mcp-server / 17487477238

05 Sep 2025 08:06AM UTC coverage: 81.348% (+0.2%) from 81.158%
17487477238

Pull #517

github

web-flow
Merge d5c4b205b into 1f68c3e5d
Pull Request #517: feat(cli): notify when a flag is wrong and suggest a fix MCP-121

937 of 1241 branches covered (75.5%)

Branch coverage included in aggregate %.

59 of 59 new or added lines in 1 file covered. (100.0%)

13 existing lines in 2 files now uncovered.

4724 of 5718 relevant lines covered (82.62%)

44.43 hits per line

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

89.47
/src/telemetry/telemetry.ts
1
import type { Session } from "../common/session.js";
2
import type { BaseEvent, CommonProperties } from "./types.js";
3
import type { UserConfig } from "../common/config.js";
4
import { LogId } from "../common/logger.js";
2✔
5
import type { ApiClient } from "../common/atlas/apiClient.js";
6
import { MACHINE_METADATA } from "./constants.js";
2✔
7
import { EventCache } from "./eventCache.js";
2✔
8
import { detectContainerEnv } from "../helpers/container.js";
2✔
9
import type { DeviceId } from "../helpers/deviceId.js";
10

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

16
export class Telemetry {
2✔
17
    private isBufferingEvents: boolean = true;
2✔
18
    /** Resolves when the setup is complete or a timeout occurs */
19
    public setupPromise: Promise<[string, boolean]> | undefined;
20
    private eventCache: EventCache;
21
    private deviceId: DeviceId;
22

23
    private constructor(
2✔
24
        private readonly session: Session,
78✔
25
        private readonly userConfig: UserConfig,
78✔
26
        private readonly commonProperties: CommonProperties,
78✔
27
        { eventCache, deviceId }: { eventCache: EventCache; deviceId: DeviceId }
78✔
28
    ) {
78✔
29
        this.eventCache = eventCache;
78✔
30
        this.deviceId = deviceId;
78✔
31
    }
78✔
32

33
    static create(
2✔
34
        session: Session,
78✔
35
        userConfig: UserConfig,
78✔
36
        deviceId: DeviceId,
78✔
37
        {
78✔
38
            commonProperties = {},
78✔
39
            eventCache = EventCache.getInstance(),
78✔
40
        }: {
78✔
41
            commonProperties?: Partial<CommonProperties>;
42
            eventCache?: EventCache;
43
        } = {}
78✔
44
    ): Telemetry {
78✔
45
        const mergedProperties = {
78✔
46
            ...MACHINE_METADATA,
78✔
47
            ...commonProperties,
78✔
48
        };
78✔
49
        const instance = new Telemetry(session, userConfig, mergedProperties, {
78✔
50
            eventCache,
78✔
51
            deviceId,
78✔
52
        });
78✔
53

54
        void instance.setup();
78✔
55
        return instance;
78✔
56
    }
78✔
57

58
    private async setup(): Promise<void> {
2✔
59
        if (!this.isTelemetryEnabled()) {
78✔
60
            return;
60✔
61
        }
60!
62

63
        this.setupPromise = Promise.all([this.deviceId.get(), detectContainerEnv()]);
18✔
64
        const [deviceIdValue, containerEnv] = await this.setupPromise;
18✔
65

66
        this.commonProperties.device_id = deviceIdValue;
18✔
67
        this.commonProperties.is_container_env = containerEnv;
18✔
68

69
        this.isBufferingEvents = false;
18✔
70
    }
78✔
71

72
    public async close(): Promise<void> {
2✔
73
        this.isBufferingEvents = false;
61✔
74
        await this.emitEvents(this.eventCache.getEvents());
61✔
75
    }
61✔
76

77
    /**
78
     * Emits events through the telemetry pipeline
79
     * @param events - The events to emit
80
     */
81
    public async emitEvents(events: BaseEvent[]): Promise<void> {
2✔
82
        try {
189✔
83
            if (!this.isTelemetryEnabled()) {
189✔
84
                this.session.logger.info({
179✔
85
                    id: LogId.telemetryEmitFailure,
179✔
86
                    context: "telemetry",
179✔
87
                    message: "Telemetry is disabled.",
179✔
88
                    noRedaction: true,
179✔
89
                });
179✔
90
                return;
179✔
91
            }
179!
92

93
            await this.emit(events);
10✔
94
        } catch {
24!
UNCOV
95
            this.session.logger.debug({
×
UNCOV
96
                id: LogId.telemetryEmitFailure,
×
UNCOV
97
                context: "telemetry",
×
UNCOV
98
                message: "Error emitting telemetry events.",
×
UNCOV
99
                noRedaction: true,
×
UNCOV
100
            });
×
UNCOV
101
        }
×
102
    }
189✔
103

104
    /**
105
     * Gets the common properties for events
106
     * @returns Object containing common properties for all events
107
     */
108
    public getCommonProperties(): CommonProperties {
2✔
109
        return {
23✔
110
            ...this.commonProperties,
23✔
111
            transport: this.userConfig.transport,
23✔
112
            mcp_client_version: this.session.mcpClient?.version,
23✔
113
            mcp_client_name: this.session.mcpClient?.name,
23✔
114
            session_id: this.session.sessionId,
23✔
115
            config_atlas_auth: this.session.apiClient.hasCredentials() ? "true" : "false",
23✔
116
            config_connection_string: this.userConfig.connectionString ? "true" : "false",
23!
117
        };
23✔
118
    }
23✔
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 {
2✔
128
        // Check if telemetry is explicitly disabled in config
129
        if (this.userConfig.telemetry === "disabled") {
611✔
130
            return false;
582✔
131
        }
582!
132

133
        const doNotTrack = "DO_NOT_TRACK" in process.env;
29✔
134
        return !doNotTrack;
29✔
135
    }
611✔
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> {
2✔
142
        if (this.isBufferingEvents) {
10!
UNCOV
143
            this.eventCache.appendEvents(events);
×
UNCOV
144
            return;
×
UNCOV
145
        }
×
146

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

150
        this.session.logger.debug({
10✔
151
            id: LogId.telemetryEmitStart,
10✔
152
            context: "telemetry",
10✔
153
            message: `Attempting to send ${allEvents.length} events (${cachedEvents.length} cached)`,
10✔
154
        });
10✔
155

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

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

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