Skip to main content
Glama

MongoDB MCP Server

Official
by mongodb-js
telemetry.ts8.52 kB
import type { Session } from "../common/session.js"; import type { BaseEvent, CommonProperties } from "./types.js"; import type { UserConfig } from "../common/config.js"; import { LogId } from "../common/logger.js"; import type { ApiClient } from "../common/atlas/apiClient.js"; import { MACHINE_METADATA } from "./constants.js"; import { EventCache } from "./eventCache.js"; import { detectContainerEnv } from "../helpers/container.js"; import type { DeviceId } from "../helpers/deviceId.js"; import { EventEmitter } from "events"; import { redact } from "mongodb-redact"; type EventResult = { success: boolean; error?: Error; }; export interface TelemetryEvents { "events-emitted": []; "events-send-failed": []; "events-skipped": []; } export class Telemetry { private isBufferingEvents: boolean = true; /** Resolves when the setup is complete or a timeout occurs */ public setupPromise: Promise<[string, boolean]> | undefined; public readonly events: EventEmitter<TelemetryEvents> = new EventEmitter(); private eventCache: EventCache; private deviceId: DeviceId; private constructor( private readonly session: Session, private readonly userConfig: UserConfig, private readonly commonProperties: CommonProperties, { eventCache, deviceId }: { eventCache: EventCache; deviceId: DeviceId } ) { this.eventCache = eventCache; this.deviceId = deviceId; } static create( session: Session, userConfig: UserConfig, deviceId: DeviceId, { commonProperties = {}, eventCache = EventCache.getInstance(), }: { commonProperties?: Partial<CommonProperties>; eventCache?: EventCache; } = {} ): Telemetry { const mergedProperties = { ...MACHINE_METADATA, ...commonProperties, }; const instance = new Telemetry(session, userConfig, mergedProperties, { eventCache, deviceId, }); void instance.setup(); return instance; } private async setup(): Promise<void> { if (!this.isTelemetryEnabled()) { this.session.logger.info({ id: LogId.telemetryEmitFailure, context: "telemetry", message: "Telemetry is disabled.", noRedaction: true, }); return; } this.setupPromise = Promise.all([this.deviceId.get(), detectContainerEnv()]); const [deviceIdValue, containerEnv] = await this.setupPromise; this.commonProperties.device_id = deviceIdValue; this.commonProperties.is_container_env = containerEnv ? "true" : "false"; this.isBufferingEvents = false; } public async close(): Promise<void> { this.isBufferingEvents = false; this.session.logger.debug({ id: LogId.telemetryClose, message: `Closing telemetry and flushing ${this.eventCache.size} events`, context: "telemetry", }); // Wait up to 5 seconds for events to be sent before closing, but don't throw if it times out const flushMaxWaitTime = 5000; let flushTimeout: NodeJS.Timeout | undefined; await Promise.race([ new Promise<void>((resolve) => { flushTimeout = setTimeout(() => { this.session.logger.debug({ id: LogId.telemetryClose, message: `Failed to flush remaining events within ${flushMaxWaitTime}ms timeout`, context: "telemetry", }); resolve(); }, flushMaxWaitTime); flushTimeout.unref(); }), this.emit([]), ]); clearTimeout(flushTimeout); } /** * Emits events through the telemetry pipeline * @param events - The events to emit */ public emitEvents(events: BaseEvent[]): void { if (!this.isTelemetryEnabled()) { this.events.emit("events-skipped"); return; } // Don't wait for events to be sent - we should not block regular server // operations on telemetry void this.emit(events); } /** * Gets the common properties for events * @returns Object containing common properties for all events */ public getCommonProperties(): CommonProperties { return { ...this.commonProperties, transport: this.userConfig.transport, mcp_client_version: this.session.mcpClient?.version, mcp_client_name: this.session.mcpClient?.name, session_id: this.session.sessionId, config_atlas_auth: this.session.apiClient.hasCredentials() ? "true" : "false", config_connection_string: this.userConfig.connectionString ? "true" : "false", }; } /** * Checks if telemetry is currently enabled * This is a method rather than a constant to capture runtime config changes * * Follows the Console Do Not Track standard (https://consoledonottrack.com/) * by respecting the DO_NOT_TRACK environment variable */ public isTelemetryEnabled(): boolean { // Check if telemetry is explicitly disabled in config if (this.userConfig.telemetry === "disabled") { return false; } const doNotTrack = "DO_NOT_TRACK" in process.env; return !doNotTrack; } /** * Attempts to emit events through authenticated and unauthenticated clients * Falls back to caching if both attempts fail */ private async emit(events: BaseEvent[]): Promise<void> { if (this.isBufferingEvents) { this.eventCache.appendEvents(events); return; } try { const cachedEvents = this.eventCache.getEvents(); const allEvents = [...cachedEvents.map((e) => e.event), ...events]; this.session.logger.debug({ id: LogId.telemetryEmitStart, context: "telemetry", message: `Attempting to send ${allEvents.length} events (${cachedEvents.length} cached)`, }); const result = await this.sendEvents(this.session.apiClient, allEvents); if (result.success) { this.eventCache.removeEvents(cachedEvents.map((e) => e.id)); this.session.logger.debug({ id: LogId.telemetryEmitSuccess, context: "telemetry", message: `Sent ${allEvents.length} events successfully: ${JSON.stringify(allEvents)}`, }); this.events.emit("events-emitted"); return; } this.session.logger.debug({ id: LogId.telemetryEmitFailure, context: "telemetry", message: `Error sending event to client: ${result.error instanceof Error ? result.error.message : String(result.error)}`, }); this.eventCache.appendEvents(events); this.events.emit("events-send-failed"); } catch (error) { this.session.logger.debug({ id: LogId.telemetryEmitFailure, context: "telemetry", message: `Error emitting telemetry events: ${error instanceof Error ? error.message : String(error)}`, noRedaction: true, }); this.events.emit("events-send-failed"); } } /** * Attempts to send events through the provided API client. * Events are redacted before being sent to ensure no sensitive data is transmitted */ private async sendEvents(client: ApiClient, events: BaseEvent[]): Promise<EventResult> { try { await client.sendEvents( events.map((event) => ({ ...event, properties: { ...redact(this.getCommonProperties(), this.session.keychain.allSecrets), ...redact(event.properties, this.session.keychain.allSecrets), }, })) ); return { success: true }; } catch (error) { return { success: false, error: error instanceof Error ? error : new Error(String(error)), }; } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mongodb-js/mongodb-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server