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

mongodb-js / mongodb-mcp-server / 19462524052

18 Nov 2025 10:20AM UTC coverage: 80.328% (+0.1%) from 80.187%
19462524052

Pull #729

github

web-flow
Merge 8e32474a4 into 53143eecd
Pull Request #729: chore: refactor config initialisation for CLI to allow easy extension from other config sources as well MCP-288

1328 of 1750 branches covered (75.89%)

Branch coverage included in aggregate %.

332 of 346 new or added lines in 12 files covered. (95.95%)

15 existing lines in 2 files now uncovered.

6406 of 7878 relevant lines covered (81.32%)

72.35 hits per line

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

90.54
/src/common/logger.ts
1
import fs from "fs/promises";
3✔
2
import type { MongoLogId, MongoLogWriter } from "mongodb-log-writer";
3
import { mongoLogId, MongoLogManager } from "mongodb-log-writer";
3✔
4
import { redact } from "mongodb-redact";
3✔
5
import type { LoggingMessageNotification } from "@modelcontextprotocol/sdk/types.js";
6
import { EventEmitter } from "events";
3✔
7
import type { Server } from "../lib.js";
8
import type { Keychain } from "./keychain.js";
9

10
export type LogLevel = LoggingMessageNotification["params"]["level"];
11

12
export const LogId = {
3✔
13
    serverStartFailure: mongoLogId(1_000_001),
3✔
14
    serverInitialized: mongoLogId(1_000_002),
3✔
15
    serverCloseRequested: mongoLogId(1_000_003),
3✔
16
    serverClosed: mongoLogId(1_000_004),
3✔
17
    serverCloseFailure: mongoLogId(1_000_005),
3✔
18
    serverDuplicateLoggers: mongoLogId(1_000_006),
3✔
19
    serverMcpClientSet: mongoLogId(1_000_007),
3✔
20

21
    atlasCheckCredentials: mongoLogId(1_001_001),
3✔
22
    atlasDeleteDatabaseUserFailure: mongoLogId(1_001_002),
3✔
23
    atlasConnectFailure: mongoLogId(1_001_003),
3✔
24
    atlasInspectFailure: mongoLogId(1_001_004),
3✔
25
    atlasConnectAttempt: mongoLogId(1_001_005),
3✔
26
    atlasConnectSucceeded: mongoLogId(1_001_006),
3✔
27
    atlasApiRevokeFailure: mongoLogId(1_001_007),
3✔
28
    atlasIpAccessListAdded: mongoLogId(1_001_008),
3✔
29
    atlasIpAccessListAddFailure: mongoLogId(1_001_009),
3✔
30

31
    telemetryDisabled: mongoLogId(1_002_001),
3✔
32
    telemetryEmitFailure: mongoLogId(1_002_002),
3✔
33
    telemetryEmitStart: mongoLogId(1_002_003),
3✔
34
    telemetryEmitSuccess: mongoLogId(1_002_004),
3✔
35
    telemetryMetadataError: mongoLogId(1_002_005),
3✔
36
    deviceIdResolutionError: mongoLogId(1_002_006),
3✔
37
    deviceIdTimeout: mongoLogId(1_002_007),
3✔
38
    telemetryClose: mongoLogId(1_002_008),
3✔
39

40
    toolExecute: mongoLogId(1_003_001),
3✔
41
    toolExecuteFailure: mongoLogId(1_003_002),
3✔
42
    toolDisabled: mongoLogId(1_003_003),
3✔
43

44
    mongodbConnectFailure: mongoLogId(1_004_001),
3✔
45
    mongodbDisconnectFailure: mongoLogId(1_004_002),
3✔
46
    mongodbConnectTry: mongoLogId(1_004_003),
3✔
47
    mongodbCursorCloseError: mongoLogId(1_004_004),
3✔
48

49
    toolUpdateFailure: mongoLogId(1_005_001),
3✔
50
    resourceUpdateFailure: mongoLogId(1_005_002),
3✔
51
    updateToolMetadata: mongoLogId(1_005_003),
3✔
52
    toolValidationError: mongoLogId(1_005_004),
3✔
53

54
    streamableHttpTransportStarted: mongoLogId(1_006_001),
3✔
55
    streamableHttpTransportSessionCloseFailure: mongoLogId(1_006_002),
3✔
56
    streamableHttpTransportSessionCloseNotification: mongoLogId(1_006_003),
3✔
57
    streamableHttpTransportSessionCloseNotificationFailure: mongoLogId(1_006_004),
3✔
58
    streamableHttpTransportRequestFailure: mongoLogId(1_006_005),
3✔
59
    streamableHttpTransportCloseFailure: mongoLogId(1_006_006),
3✔
60
    streamableHttpTransportKeepAliveFailure: mongoLogId(1_006_007),
3✔
61
    streamableHttpTransportKeepAlive: mongoLogId(1_006_008),
3✔
62
    streamableHttpTransportHttpHostWarning: mongoLogId(1_006_009),
3✔
63

64
    exportCleanupError: mongoLogId(1_007_001),
3✔
65
    exportCreationError: mongoLogId(1_007_002),
3✔
66
    exportCreationCleanupError: mongoLogId(1_007_003),
3✔
67
    exportReadError: mongoLogId(1_007_004),
3✔
68
    exportCloseError: mongoLogId(1_007_005),
3✔
69
    exportedDataListError: mongoLogId(1_007_006),
3✔
70
    exportedDataAutoCompleteError: mongoLogId(1_007_007),
3✔
71
    exportLockError: mongoLogId(1_007_008),
3✔
72

73
    oidcFlow: mongoLogId(1_008_001),
3✔
74

75
    atlasPaSuggestedIndexesFailure: mongoLogId(1_009_001),
3✔
76
    atlasPaDropIndexSuggestionsFailure: mongoLogId(1_009_002),
3✔
77
    atlasPaSchemaAdviceFailure: mongoLogId(1_009_003),
3✔
78
    atlasPaSlowQueryLogsFailure: mongoLogId(1_009_004),
3✔
79
} as const;
3✔
80

81
export interface LogPayload {
82
    id: MongoLogId;
83
    context: string;
84
    message: string;
85
    noRedaction?: boolean | LoggerType | LoggerType[];
86
    attributes?: Record<string, string>;
87
}
88

89
export type LoggerType = "console" | "disk" | "mcp";
90

91
// eslint-disable-next-line @typescript-eslint/no-explicit-any
92
type EventMap<T> = Record<keyof T, any[]> | DefaultEventMap;
93
type DefaultEventMap = [never];
94

95
export abstract class LoggerBase<T extends EventMap<T> = DefaultEventMap> extends EventEmitter<T> {
3✔
96
    private readonly defaultUnredactedLogger: LoggerType = "mcp";
3✔
97

98
    constructor(private readonly keychain: Keychain | undefined) {
3✔
99
        super();
236✔
100
    }
236✔
101

102
    public log(level: LogLevel, payload: LogPayload): void {
3✔
103
        // If no explicit value is supplied for unredacted loggers, default to "mcp"
104
        const noRedaction = payload.noRedaction !== undefined ? payload.noRedaction : this.defaultUnredactedLogger;
96✔
105

106
        this.logCore(level, {
96✔
107
            ...payload,
96✔
108
            message: this.redactIfNecessary(payload.message, noRedaction),
96✔
109
        });
96✔
110
    }
96✔
111

112
    protected abstract readonly type?: LoggerType;
113

114
    protected abstract logCore(level: LogLevel, payload: LogPayload): void;
115

116
    private redactIfNecessary(message: string, noRedaction: LogPayload["noRedaction"]): string {
3✔
117
        if (typeof noRedaction === "boolean" && noRedaction) {
96✔
118
            // If the consumer has supplied noRedaction: true, we don't redact the log message
119
            // regardless of the logger type
120
            return message;
16✔
121
        }
16✔
122

123
        if (typeof noRedaction === "string" && noRedaction === this.type) {
96✔
124
            // If the consumer has supplied noRedaction: logger-type, we skip redacting if
125
            // our logger type is the same as what the consumer requested
126
            return message;
7✔
127
        }
7✔
128

129
        if (
73✔
130
            typeof noRedaction === "object" &&
73✔
131
            Array.isArray(noRedaction) &&
4✔
132
            this.type &&
4✔
133
            noRedaction.indexOf(this.type) !== -1
4✔
134
        ) {
96✔
135
            // If the consumer has supplied noRedaction: array, we skip redacting if our logger
136
            // type is included in that array
137
            return message;
3✔
138
        }
3✔
139

140
        return redact(message, this.keychain?.allSecrets ?? []);
96✔
141
    }
96✔
142

143
    public info(payload: LogPayload): void {
3✔
144
        this.log("info", payload);
1,955✔
145
    }
1,955✔
146

147
    public error(payload: LogPayload): void {
3✔
148
        this.log("error", payload);
123✔
149
    }
123✔
150
    public debug(payload: LogPayload): void {
3✔
151
        this.log("debug", payload);
1,505✔
152
    }
1,505✔
153

154
    public notice(payload: LogPayload): void {
3✔
155
        this.log("notice", payload);
2✔
156
    }
2✔
157

158
    public warning(payload: LogPayload): void {
3✔
159
        this.log("warning", payload);
2✔
160
    }
2✔
161

162
    public critical(payload: LogPayload): void {
3✔
163
        this.log("critical", payload);
×
164
    }
×
165

166
    public alert(payload: LogPayload): void {
3✔
167
        this.log("alert", payload);
×
168
    }
×
169

170
    public emergency(payload: LogPayload): void {
3✔
171
        this.log("emergency", payload);
×
172
    }
×
173

174
    protected mapToMongoDBLogLevel(level: LogLevel): "info" | "warn" | "error" | "debug" | "fatal" {
3✔
175
        switch (level) {
2✔
176
            case "info":
2✔
177
                return "info";
2✔
178
            case "warning":
2!
179
                return "warn";
×
180
            case "error":
2!
181
                return "error";
×
182
            case "notice":
2!
183
            case "debug":
2!
UNCOV
184
                return "debug";
×
185
            case "critical":
2!
186
            case "alert":
2!
187
            case "emergency":
2!
188
                return "fatal";
×
189
            default:
2!
190
                return "info";
×
191
        }
2✔
192
    }
2✔
193
}
3✔
194

195
export class ConsoleLogger extends LoggerBase {
3✔
196
    protected readonly type: LoggerType = "console";
3✔
197

198
    public constructor(keychain: Keychain) {
3✔
199
        super(keychain);
21✔
200
    }
21✔
201

202
    protected logCore(level: LogLevel, payload: LogPayload): void {
3✔
203
        const { id, context, message } = payload;
61✔
204
        console.error(
61✔
205
            `[${level.toUpperCase()}] ${id.__value} - ${context}: ${message} (${process.pid}${this.serializeAttributes(payload.attributes)})`
61✔
206
        );
61✔
207
    }
61✔
208

209
    private serializeAttributes(attributes?: Record<string, string>): string {
3✔
210
        if (!attributes || Object.keys(attributes).length === 0) {
61✔
211
            return "";
27✔
212
        }
27✔
213
        return `, ${Object.entries(attributes)
34✔
214
            .map(([key, value]) => `${key}=${value}`)
34✔
215
            .join(", ")}`;
34✔
216
    }
61✔
217
}
3✔
218

219
export class DiskLogger extends LoggerBase<{ initialized: [] }> {
3✔
220
    private bufferedMessages: { level: LogLevel; payload: LogPayload }[] = [];
3✔
221
    private logWriter?: MongoLogWriter;
222

223
    public constructor(logPath: string, onError: (error: Error) => void, keychain: Keychain) {
3✔
224
        super(keychain);
2✔
225

226
        void this.initialize(logPath, onError);
2✔
227
    }
2✔
228

229
    private async initialize(logPath: string, onError: (error: Error) => void): Promise<void> {
3✔
230
        try {
2✔
231
            await fs.mkdir(logPath, { recursive: true });
2✔
232

233
            const manager = new MongoLogManager({
2✔
234
                directory: logPath,
2✔
235
                retentionDays: 30,
2✔
236
                onwarn: console.warn,
2✔
237
                onerror: console.error,
2✔
238
                gzip: false,
2✔
239
                retentionGB: 1,
2✔
240
            });
2✔
241

242
            await manager.cleanupOldLogFiles();
2✔
243

244
            this.logWriter = await manager.createLogWriter();
2✔
245

246
            for (const message of this.bufferedMessages) {
2✔
247
                this.logCore(message.level, message.payload);
2✔
248
            }
2✔
249
            this.bufferedMessages = [];
2✔
250
            this.emit("initialized");
2✔
251
        } catch (error: unknown) {
2!
252
            onError(error as Error);
×
253
        }
×
254
    }
2✔
255

256
    protected type: LoggerType = "disk";
3✔
257

258
    protected logCore(level: LogLevel, payload: LogPayload): void {
3✔
259
        if (!this.logWriter) {
4✔
260
            // If the log writer is not initialized, buffer the message
261
            this.bufferedMessages.push({ level, payload });
2✔
262
            return;
2✔
263
        }
2✔
264

265
        const { id, context, message } = payload;
2✔
266
        const mongoDBLevel = this.mapToMongoDBLogLevel(level);
2✔
267

268
        this.logWriter[mongoDBLevel]("MONGODB-MCP", id, context, message, payload.attributes);
2✔
269
    }
4✔
270
}
3✔
271

272
export class McpLogger extends LoggerBase {
3✔
273
    public static readonly LOG_LEVELS: LogLevel[] = [
3✔
274
        "debug",
3✔
275
        "info",
3✔
276
        "notice",
3✔
277
        "warning",
3✔
278
        "error",
3✔
279
        "critical",
3✔
280
        "alert",
3✔
281
        "emergency",
3✔
282
    ] as const;
3✔
283

284
    public constructor(
3✔
285
        private readonly server: Server,
16✔
286
        keychain: Keychain
16✔
287
    ) {
16✔
288
        super(keychain);
16✔
289
    }
16✔
290

291
    protected readonly type: LoggerType = "mcp";
16✔
292

293
    protected logCore(level: LogLevel, payload: LogPayload): void {
3✔
294
        // Only log if the server is connected
295
        if (!this.server.mcpServer.isConnected()) {
12!
UNCOV
296
            return;
×
UNCOV
297
        }
×
298

299
        const minimumLevel = McpLogger.LOG_LEVELS.indexOf(this.server.mcpLogLevel);
12✔
300
        const currentLevel = McpLogger.LOG_LEVELS.indexOf(level);
12✔
301
        if (minimumLevel > currentLevel) {
12✔
302
            // Don't log if the requested level is lower than the minimum level
303
            return;
1✔
304
        }
1✔
305

306
        void this.server.mcpServer.server.sendLoggingMessage({
11✔
307
            level,
11✔
308
            data: `[${payload.context}]: ${payload.message}`,
11✔
309
        });
11✔
310
    }
12✔
311
}
3✔
312

313
export class CompositeLogger extends LoggerBase {
3✔
314
    protected readonly type?: LoggerType;
315

316
    private readonly loggers: LoggerBase[] = [];
3✔
317
    private readonly attributes: Record<string, string> = {};
3✔
318

319
    constructor(...loggers: LoggerBase[]) {
3✔
320
        // composite logger does not redact, only the actual delegates do the work
321
        // so we don't need the Keychain here
322
        super(undefined);
156✔
323

324
        this.loggers = loggers;
156✔
325
    }
156✔
326

327
    public addLogger(logger: LoggerBase): void {
3✔
UNCOV
328
        this.loggers.push(logger);
×
UNCOV
329
    }
×
330

331
    public log(level: LogLevel, payload: LogPayload): void {
3✔
332
        // Override the public method to avoid the base logger redacting the message payload
333
        for (const logger of this.loggers) {
3,596!
334
            const attributes =
102✔
335
                Object.keys(this.attributes).length > 0 || payload.attributes
102✔
336
                    ? { ...this.attributes, ...payload.attributes }
67✔
337
                    : undefined;
35✔
338
            logger.log(level, { ...payload, attributes });
102✔
339
        }
102✔
340
    }
3,596✔
341

342
    protected logCore(): void {
3✔
343
        throw new Error("logCore should never be invoked on CompositeLogger");
×
344
    }
×
345

346
    public setAttribute(key: string, value: string): void {
3✔
347
        this.attributes[key] = value;
9✔
348
    }
9✔
349
}
3✔
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