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

mongodb-js / mongodb-mcp-server / 17155288437

22 Aug 2025 12:29PM UTC coverage: 82.014% (-0.03%) from 82.04%
17155288437

Pull #468

github

web-flow
Merge 3bb127c20 into ca68195c4
Pull Request #468: chore: update readme with the new tool and resources

855 of 1075 branches covered (79.53%)

Branch coverage included in aggregate %.

2 of 2 new or added lines in 2 files covered. (100.0%)

97 existing lines in 8 files now uncovered.

4357 of 5280 relevant lines covered (82.52%)

70.4 hits per line

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

89.22
/src/common/logger.ts
1
import fs from "fs/promises";
2✔
2
import { mongoLogId, MongoLogId, MongoLogManager, MongoLogWriter } from "mongodb-log-writer";
2✔
3
import redact from "mongodb-redact";
2✔
4
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
import { LoggingMessageNotification } from "@modelcontextprotocol/sdk/types.js";
6
import { EventEmitter } from "events";
2✔
7

8
export type LogLevel = LoggingMessageNotification["params"]["level"];
9

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

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

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

37
    toolExecute: mongoLogId(1_003_001),
2✔
38
    toolExecuteFailure: mongoLogId(1_003_002),
2✔
39
    toolDisabled: mongoLogId(1_003_003),
2✔
40

41
    mongodbConnectFailure: mongoLogId(1_004_001),
2✔
42
    mongodbDisconnectFailure: mongoLogId(1_004_002),
2✔
43

44
    toolUpdateFailure: mongoLogId(1_005_001),
2✔
45
    resourceUpdateFailure: mongoLogId(1_005_002),
2✔
46

47
    streamableHttpTransportStarted: mongoLogId(1_006_001),
2✔
48
    streamableHttpTransportSessionCloseFailure: mongoLogId(1_006_002),
2✔
49
    streamableHttpTransportSessionCloseNotification: mongoLogId(1_006_003),
2✔
50
    streamableHttpTransportSessionCloseNotificationFailure: mongoLogId(1_006_004),
2✔
51
    streamableHttpTransportRequestFailure: mongoLogId(1_006_005),
2✔
52
    streamableHttpTransportCloseFailure: mongoLogId(1_006_006),
2✔
53

54
    exportCleanupError: mongoLogId(1_007_001),
2✔
55
    exportCreationError: mongoLogId(1_007_002),
2✔
56
    exportCreationCleanupError: mongoLogId(1_007_003),
2✔
57
    exportReadError: mongoLogId(1_007_004),
2✔
58
    exportCloseError: mongoLogId(1_007_005),
2✔
59
    exportedDataListError: mongoLogId(1_007_006),
2✔
60
    exportedDataAutoCompleteError: mongoLogId(1_007_007),
2✔
61
    exportLockError: mongoLogId(1_007_008),
2✔
62

63
    oidcFlow: mongoLogId(1_008_001),
2✔
64
} as const;
2✔
65

66
interface LogPayload {
67
    id: MongoLogId;
68
    context: string;
69
    message: string;
70
    noRedaction?: boolean | LoggerType | LoggerType[];
71
    attributes?: Record<string, string>;
72
}
73

74
export type LoggerType = "console" | "disk" | "mcp";
75

76
// eslint-disable-next-line @typescript-eslint/no-explicit-any
77
type EventMap<T> = Record<keyof T, any[]> | DefaultEventMap;
78
type DefaultEventMap = [never];
79

80
export abstract class LoggerBase<T extends EventMap<T> = DefaultEventMap> extends EventEmitter<T> {
2✔
81
    private readonly defaultUnredactedLogger: LoggerType = "mcp";
280✔
82

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

87
        this.logCore(level, {
86✔
88
            ...payload,
86✔
89
            message: this.redactIfNecessary(payload.message, noRedaction),
86✔
90
        });
86✔
91
    }
86✔
92

93
    protected abstract readonly type?: LoggerType;
94

95
    protected abstract logCore(level: LogLevel, payload: LogPayload): void;
96

97
    private redactIfNecessary(message: string, noRedaction: LogPayload["noRedaction"]): string {
2✔
98
        if (typeof noRedaction === "boolean" && noRedaction) {
86✔
99
            // If the consumer has supplied noRedaction: true, we don't redact the log message
100
            // regardless of the logger type
101
            return message;
20✔
102
        }
20✔
103

104
        if (typeof noRedaction === "string" && noRedaction === this.type) {
86✔
105
            // If the consumer has supplied noRedaction: logger-type, we skip redacting if
106
            // our logger type is the same as what the consumer requested
107
            return message;
8✔
108
        }
8✔
109

110
        if (
58✔
111
            typeof noRedaction === "object" &&
58✔
112
            Array.isArray(noRedaction) &&
8✔
113
            this.type &&
8✔
114
            noRedaction.indexOf(this.type) !== -1
8✔
115
        ) {
86✔
116
            // If the consumer has supplied noRedaction: array, we skip redacting if our logger
117
            // type is included in that array
118
            return message;
6✔
119
        }
6✔
120

121
        return redact(message);
52✔
122
    }
86✔
123

124
    public info(payload: LogPayload): void {
2✔
125
        this.log("info", payload);
365✔
126
    }
365✔
127

128
    public error(payload: LogPayload): void {
2✔
129
        this.log("error", payload);
113✔
130
    }
113✔
131
    public debug(payload: LogPayload): void {
2✔
132
        this.log("debug", payload);
815✔
133
    }
815✔
134

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

139
    public warning(payload: LogPayload): void {
2✔
140
        this.log("warning", payload);
4✔
141
    }
4✔
142

143
    public critical(payload: LogPayload): void {
2✔
144
        this.log("critical", payload);
×
UNCOV
145
    }
×
146

147
    public alert(payload: LogPayload): void {
2✔
148
        this.log("alert", payload);
×
UNCOV
149
    }
×
150

151
    public emergency(payload: LogPayload): void {
2✔
152
        this.log("emergency", payload);
×
UNCOV
153
    }
×
154
}
2✔
155

156
export class ConsoleLogger extends LoggerBase {
2✔
157
    protected readonly type: LoggerType = "console";
28✔
158

159
    protected logCore(level: LogLevel, payload: LogPayload): void {
2✔
160
        const { id, context, message } = payload;
36✔
161
        console.error(
36✔
162
            `[${level.toUpperCase()}] ${id.__value} - ${context}: ${message} (${process.pid}${this.serializeAttributes(payload.attributes)})`
36✔
163
        );
36✔
164
    }
36✔
165

166
    private serializeAttributes(attributes?: Record<string, string>): string {
2✔
167
        if (!attributes || Object.keys(attributes).length === 0) {
36✔
168
            return "";
20✔
169
        }
20✔
170
        return `, ${Object.entries(attributes)
16✔
171
            .map(([key, value]) => `${key}=${value}`)
16✔
172
            .join(", ")}`;
16✔
173
    }
36✔
174
}
2✔
175

176
export class DiskLogger extends LoggerBase<{ initialized: [] }> {
2✔
177
    private bufferedMessages: { level: LogLevel; payload: LogPayload }[] = [];
2✔
178
    private logWriter?: MongoLogWriter;
179

180
    public constructor(logPath: string, onError: (error: Error) => void) {
2✔
181
        super();
4✔
182

183
        void this.initialize(logPath, onError);
4✔
184
    }
4✔
185

186
    private async initialize(logPath: string, onError: (error: Error) => void): Promise<void> {
2✔
187
        try {
4✔
188
            await fs.mkdir(logPath, { recursive: true });
4✔
189

190
            const manager = new MongoLogManager({
4✔
191
                directory: logPath,
4✔
192
                retentionDays: 30,
4✔
193
                onwarn: console.warn,
4✔
194
                onerror: console.error,
4✔
195
                gzip: false,
4✔
196
                retentionGB: 1,
4✔
197
            });
4✔
198

199
            await manager.cleanupOldLogFiles();
4✔
200

201
            this.logWriter = await manager.createLogWriter();
4✔
202

203
            for (const message of this.bufferedMessages) {
4✔
204
                this.logCore(message.level, message.payload);
4✔
205
            }
4✔
206
            this.bufferedMessages = [];
4✔
207
            this.emit("initialized");
4✔
208
        } catch (error: unknown) {
4!
209
            onError(error as Error);
×
UNCOV
210
        }
×
211
    }
4✔
212

213
    protected type: LoggerType = "disk";
2✔
214

215
    protected logCore(level: LogLevel, payload: LogPayload): void {
2✔
216
        if (!this.logWriter) {
8✔
217
            // If the log writer is not initialized, buffer the message
218
            this.bufferedMessages.push({ level, payload });
4✔
219
            return;
4✔
220
        }
4✔
221

222
        const { id, context, message } = payload;
4✔
223
        const mongoDBLevel = this.mapToMongoDBLogLevel(level);
4✔
224

225
        this.logWriter[mongoDBLevel]("MONGODB-MCP", id, context, message, payload.attributes);
4✔
226
    }
8✔
227

228
    private mapToMongoDBLogLevel(level: LogLevel): "info" | "warn" | "error" | "debug" | "fatal" {
2✔
229
        switch (level) {
4✔
230
            case "info":
4✔
231
                return "info";
4✔
232
            case "warning":
4!
UNCOV
233
                return "warn";
×
234
            case "error":
4!
UNCOV
235
                return "error";
×
236
            case "notice":
4!
237
            case "debug":
4!
UNCOV
238
                return "debug";
×
239
            case "critical":
4!
240
            case "alert":
4!
241
            case "emergency":
4!
UNCOV
242
                return "fatal";
×
243
            default:
4!
UNCOV
244
                return "info";
×
245
        }
4✔
246
    }
4✔
247
}
2✔
248

249
export class McpLogger extends LoggerBase {
2✔
250
    public constructor(private readonly server: McpServer) {
2✔
251
        super();
26✔
252
    }
26✔
253

254
    protected readonly type: LoggerType = "mcp";
26✔
255

256
    protected logCore(level: LogLevel, payload: LogPayload): void {
2✔
257
        // Only log if the server is connected
258
        if (!this.server?.isConnected()) {
18!
259
            return;
×
UNCOV
260
        }
×
261

262
        void this.server.server.sendLoggingMessage({
18✔
263
            level,
18✔
264
            data: `[${payload.context}]: ${payload.message}`,
18✔
265
        });
18✔
266
    }
18✔
267
}
2✔
268

269
export class CompositeLogger extends LoggerBase {
2✔
270
    protected readonly type?: LoggerType;
271

272
    private readonly loggers: LoggerBase[] = [];
2✔
273
    private readonly attributes: Record<string, string> = {};
2✔
274

275
    constructor(...loggers: LoggerBase[]) {
2✔
276
        super();
158✔
277

278
        this.loggers = loggers;
158✔
279
    }
158✔
280

281
    public addLogger(logger: LoggerBase): void {
2✔
282
        this.loggers.push(logger);
×
UNCOV
283
    }
×
284

285
    public log(level: LogLevel, payload: LogPayload): void {
2✔
286
        // Override the public method to avoid the base logger redacting the message payload
287
        for (const logger of this.loggers) {
1,257✔
288
            logger.log(level, { ...payload, attributes: { ...this.attributes, ...payload.attributes } });
42✔
289
        }
42✔
290
    }
1,257✔
291

292
    protected logCore(): void {
2✔
293
        throw new Error("logCore should never be invoked on CompositeLogger");
×
UNCOV
294
    }
×
295

296
    public setAttribute(key: string, value: string): void {
2✔
297
        this.attributes[key] = value;
8✔
298
    }
8✔
299
}
2✔
300

301
export class NullLogger extends LoggerBase {
2✔
302
    protected type?: LoggerType;
303

304
    protected logCore(): void {
2✔
305
        // No-op logger, does not log anything
306
    }
28✔
307
}
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