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

mongodb-js / mongodb-mcp-server / 17657973871

11 Sep 2025 09:37PM UTC coverage: 81.59% (+0.4%) from 81.188%
17657973871

Pull #524

github

web-flow
Merge 7617674d0 into d6b84c7bd
Pull Request #524: chore: update smithery dockerfile to be closer to the official one

962 of 1279 branches covered (75.22%)

Branch coverage included in aggregate %.

4866 of 5864 relevant lines covered (82.98%)

46.3 hits per line

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

92.97
/src/common/logger.ts
1
import fs from "fs/promises";
2✔
2
import type { MongoLogId, MongoLogWriter } from "mongodb-log-writer";
3
import { mongoLogId, MongoLogManager } from "mongodb-log-writer";
2✔
4
import { redact } from "mongodb-redact";
2✔
5
import type { LoggingMessageNotification } from "@modelcontextprotocol/sdk/types.js";
6
import { EventEmitter } from "events";
2✔
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 = {
2✔
13
    serverStartFailure: mongoLogId(1_000_001),
2✔
14
    serverInitialized: mongoLogId(1_000_002),
2✔
15
    serverCloseRequested: mongoLogId(1_000_003),
2✔
16
    serverClosed: mongoLogId(1_000_004),
2✔
17
    serverCloseFailure: mongoLogId(1_000_005),
2✔
18
    serverDuplicateLoggers: mongoLogId(1_000_006),
2✔
19
    serverMcpClientSet: mongoLogId(1_000_007),
2✔
20

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

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

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

44
    mongodbConnectFailure: mongoLogId(1_004_001),
2✔
45
    mongodbDisconnectFailure: mongoLogId(1_004_002),
2✔
46
    mongodbConnectTry: mongoLogId(1_004_003),
2✔
47

48
    toolUpdateFailure: mongoLogId(1_005_001),
2✔
49
    resourceUpdateFailure: mongoLogId(1_005_002),
2✔
50

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

61
    exportCleanupError: mongoLogId(1_007_001),
2✔
62
    exportCreationError: mongoLogId(1_007_002),
2✔
63
    exportCreationCleanupError: mongoLogId(1_007_003),
2✔
64
    exportReadError: mongoLogId(1_007_004),
2✔
65
    exportCloseError: mongoLogId(1_007_005),
2✔
66
    exportedDataListError: mongoLogId(1_007_006),
2✔
67
    exportedDataAutoCompleteError: mongoLogId(1_007_007),
2✔
68
    exportLockError: mongoLogId(1_007_008),
2✔
69

70
    oidcFlow: mongoLogId(1_008_001),
2✔
71
} as const;
2✔
72

73
export interface LogPayload {
74
    id: MongoLogId;
75
    context: string;
76
    message: string;
77
    noRedaction?: boolean | LoggerType | LoggerType[];
78
    attributes?: Record<string, string>;
79
}
80

81
export type LoggerType = "console" | "disk" | "mcp";
82

83
// eslint-disable-next-line @typescript-eslint/no-explicit-any
84
type EventMap<T> = Record<keyof T, any[]> | DefaultEventMap;
85
type DefaultEventMap = [never];
86

87
export abstract class LoggerBase<T extends EventMap<T> = DefaultEventMap> extends EventEmitter<T> {
2✔
88
    private readonly defaultUnredactedLogger: LoggerType = "mcp";
2✔
89

90
    constructor(private readonly keychain: Keychain | undefined) {
2✔
91
        super();
183✔
92
    }
183✔
93

94
    public log(level: LogLevel, payload: LogPayload): void {
2✔
95
        // If no explicit value is supplied for unredacted loggers, default to "mcp"
96
        const noRedaction = payload.noRedaction !== undefined ? payload.noRedaction : this.defaultUnredactedLogger;
100✔
97

98
        this.logCore(level, {
100✔
99
            ...payload,
100✔
100
            message: this.redactIfNecessary(payload.message, noRedaction),
100✔
101
        });
100✔
102
    }
100✔
103

104
    protected abstract readonly type?: LoggerType;
105

106
    protected abstract logCore(level: LogLevel, payload: LogPayload): void;
107

108
    private redactIfNecessary(message: string, noRedaction: LogPayload["noRedaction"]): string {
2✔
109
        if (typeof noRedaction === "boolean" && noRedaction) {
100✔
110
            // If the consumer has supplied noRedaction: true, we don't redact the log message
111
            // regardless of the logger type
112
            return message;
14✔
113
        }
14✔
114

115
        if (typeof noRedaction === "string" && noRedaction === this.type) {
100✔
116
            // If the consumer has supplied noRedaction: logger-type, we skip redacting if
117
            // our logger type is the same as what the consumer requested
118
            return message;
23✔
119
        }
23✔
120

121
        if (
63✔
122
            typeof noRedaction === "object" &&
63✔
123
            Array.isArray(noRedaction) &&
4✔
124
            this.type &&
4✔
125
            noRedaction.indexOf(this.type) !== -1
4✔
126
        ) {
100✔
127
            // If the consumer has supplied noRedaction: array, we skip redacting if our logger
128
            // type is included in that array
129
            return message;
3✔
130
        }
3✔
131

132
        return redact(message, this.keychain?.allSecrets ?? []);
100✔
133
    }
100✔
134

135
    public info(payload: LogPayload): void {
2✔
136
        this.log("info", payload);
154✔
137
    }
154✔
138

139
    public error(payload: LogPayload): void {
2✔
140
        this.log("error", payload);
80✔
141
    }
80✔
142
    public debug(payload: LogPayload): void {
2✔
143
        this.log("debug", payload);
1,015✔
144
    }
1,015✔
145

146
    public notice(payload: LogPayload): void {
2✔
147
        this.log("notice", payload);
2✔
148
    }
2✔
149

150
    public warning(payload: LogPayload): void {
2✔
151
        this.log("warning", payload);
3✔
152
    }
3✔
153

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

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

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

166
    protected mapToMongoDBLogLevel(level: LogLevel): "info" | "warn" | "error" | "debug" | "fatal" {
2✔
167
        switch (level) {
19✔
168
            case "info":
19✔
169
                return "info";
5✔
170
            case "warning":
19!
171
                return "warn";
×
172
            case "error":
19!
173
                return "error";
×
174
            case "notice":
19!
175
            case "debug":
19✔
176
                return "debug";
14✔
177
            case "critical":
19!
178
            case "alert":
19!
179
            case "emergency":
19!
180
                return "fatal";
×
181
            default:
19!
182
                return "info";
×
183
        }
19✔
184
    }
19✔
185
}
2✔
186

187
export class ConsoleLogger extends LoggerBase {
2✔
188
    protected readonly type: LoggerType = "console";
2✔
189

190
    public constructor(keychain: Keychain) {
2✔
191
        super(keychain);
17✔
192
    }
17✔
193

194
    protected logCore(level: LogLevel, payload: LogPayload): void {
2✔
195
        const { id, context, message } = payload;
35✔
196
        console.error(
35✔
197
            `[${level.toUpperCase()}] ${id.__value} - ${context}: ${message} (${process.pid}${this.serializeAttributes(payload.attributes)})`
35✔
198
        );
35✔
199
    }
35✔
200

201
    private serializeAttributes(attributes?: Record<string, string>): string {
2✔
202
        if (!attributes || Object.keys(attributes).length === 0) {
35✔
203
            return "";
15✔
204
        }
15✔
205
        return `, ${Object.entries(attributes)
20✔
206
            .map(([key, value]) => `${key}=${value}`)
20✔
207
            .join(", ")}`;
20✔
208
    }
35✔
209
}
2✔
210

211
export class DiskLogger extends LoggerBase<{ initialized: [] }> {
2✔
212
    private bufferedMessages: { level: LogLevel; payload: LogPayload }[] = [];
2✔
213
    private logWriter?: MongoLogWriter;
214

215
    public constructor(logPath: string, onError: (error: Error) => void, keychain: Keychain) {
2✔
216
        super(keychain);
6✔
217

218
        void this.initialize(logPath, onError);
6✔
219
    }
6✔
220

221
    private async initialize(logPath: string, onError: (error: Error) => void): Promise<void> {
2✔
222
        try {
6✔
223
            await fs.mkdir(logPath, { recursive: true });
6✔
224

225
            const manager = new MongoLogManager({
6✔
226
                directory: logPath,
6✔
227
                retentionDays: 30,
6✔
228
                onwarn: console.warn,
6✔
229
                onerror: console.error,
6✔
230
                gzip: false,
6✔
231
                retentionGB: 1,
6✔
232
            });
6✔
233

234
            await manager.cleanupOldLogFiles();
6✔
235

236
            this.logWriter = await manager.createLogWriter();
3✔
237

238
            for (const message of this.bufferedMessages) {
3✔
239
                this.logCore(message.level, message.payload);
3✔
240
            }
3✔
241
            this.bufferedMessages = [];
3✔
242
            this.emit("initialized");
3✔
243
        } catch (error: unknown) {
6!
244
            onError(error as Error);
×
245
        }
×
246
    }
6✔
247

248
    protected type: LoggerType = "disk";
2✔
249

250
    protected logCore(level: LogLevel, payload: LogPayload): void {
2✔
251
        if (!this.logWriter) {
25✔
252
            // If the log writer is not initialized, buffer the message
253
            this.bufferedMessages.push({ level, payload });
6✔
254
            return;
6✔
255
        }
6✔
256

257
        const { id, context, message } = payload;
19✔
258
        const mongoDBLevel = this.mapToMongoDBLogLevel(level);
19✔
259

260
        this.logWriter[mongoDBLevel]("MONGODB-MCP", id, context, message, payload.attributes);
19✔
261
    }
25✔
262
}
2✔
263

264
export class McpLogger extends LoggerBase {
2✔
265
    public static readonly LOG_LEVELS: LogLevel[] = [
54✔
266
        "debug",
54✔
267
        "info",
54✔
268
        "notice",
54✔
269
        "warning",
54✔
270
        "error",
54✔
271
        "critical",
54✔
272
        "alert",
54✔
273
        "emergency",
54✔
274
    ] as const;
54✔
275

276
    public constructor(
54✔
277
        private readonly server: Server,
18✔
278
        keychain: Keychain
18✔
279
    ) {
18✔
280
        super(keychain);
18✔
281
    }
18✔
282

283
    protected readonly type: LoggerType = "mcp";
18✔
284

285
    protected logCore(level: LogLevel, payload: LogPayload): void {
54✔
286
        // Only log if the server is connected
287
        if (!this.server.mcpServer.isConnected()) {
28✔
288
            return;
8✔
289
        }
8✔
290

291
        const minimumLevel = McpLogger.LOG_LEVELS.indexOf(this.server.mcpLogLevel);
20✔
292
        const currentLevel = McpLogger.LOG_LEVELS.indexOf(level);
20✔
293
        if (minimumLevel > currentLevel) {
28✔
294
            // Don't log if the requested level is lower than the minimum level
295
            return;
1✔
296
        }
1✔
297

298
        void this.server.mcpServer.server.sendLoggingMessage({
19✔
299
            level,
19✔
300
            data: `[${payload.context}]: ${payload.message}`,
19✔
301
        });
19✔
302
    }
28✔
303
}
54✔
304

305
export class CompositeLogger extends LoggerBase {
2✔
306
    protected readonly type?: LoggerType;
307

308
    private readonly loggers: LoggerBase[] = [];
2✔
309
    private readonly attributes: Record<string, string> = {};
2✔
310

311
    constructor(...loggers: LoggerBase[]) {
2✔
312
        // composite logger does not redact, only the actual delegates do the work
313
        // so we don't need the Keychain here
314
        super(undefined);
107✔
315

316
        this.loggers = loggers;
107✔
317
    }
107✔
318

319
    public addLogger(logger: LoggerBase): void {
2✔
320
        this.loggers.push(logger);
2✔
321
    }
2✔
322

323
    public log(level: LogLevel, payload: LogPayload): void {
2✔
324
        // Override the public method to avoid the base logger redacting the message payload
325
        for (const logger of this.loggers) {
1,263!
326
            const attributes =
106✔
327
                Object.keys(this.attributes).length > 0 || payload.attributes
106✔
328
                    ? { ...this.attributes, ...payload.attributes }
87✔
329
                    : undefined;
19✔
330
            logger.log(level, { ...payload, attributes });
106✔
331
        }
106✔
332
    }
1,263✔
333

334
    protected logCore(): void {
2✔
335
        throw new Error("logCore should never be invoked on CompositeLogger");
×
336
    }
×
337

338
    public setAttribute(key: string, value: string): void {
2✔
339
        this.attributes[key] = value;
9✔
340
    }
9✔
341
}
2✔
342

343
export class NullLogger extends LoggerBase {
2✔
344
    protected type?: LoggerType;
345

346
    constructor() {
2✔
347
        super(undefined);
34✔
348
    }
34✔
349

350
    protected logCore(): void {
2✔
351
        // No-op logger, does not log anything
352
    }
14✔
353
}
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