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

mongodb-js / mongodb-mcp-server / 16790782045

06 Aug 2025 11:09PM UTC coverage: 81.233% (-0.07%) from 81.298%
16790782045

Pull #425

github

web-flow
Merge 7712da066 into 53ac631ea
Pull Request #425: fix: remove global logger MCP-103

681 of 881 branches covered (77.3%)

Branch coverage included in aggregate %.

158 of 207 new or added lines in 17 files covered. (76.33%)

6 existing lines in 2 files now uncovered.

3522 of 4293 relevant lines covered (82.04%)

58.12 hits per line

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

88.8
/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

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

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

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

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

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

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

54
interface LogPayload {
55
    id: MongoLogId;
56
    context: string;
57
    message: string;
58
    noRedaction?: boolean | LoggerType | LoggerType[];
59
    attributes?: Record<string, string>;
60
}
61

62
export type LoggerType = "console" | "disk" | "mcp";
63

64
// eslint-disable-next-line @typescript-eslint/no-explicit-any
65
type EventMap<T> = Record<keyof T, any[]> | DefaultEventMap;
66
type DefaultEventMap = [never];
67

68
export abstract class LoggerBase<T extends EventMap<T> = DefaultEventMap> extends EventEmitter<T> {
2✔
69
    private readonly defaultUnredactedLogger: LoggerType = "mcp";
214✔
70

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

75
        this.logCore(level, {
91✔
76
            ...payload,
91✔
77
            message: this.redactIfNecessary(payload.message, noRedaction),
91✔
78
        });
91✔
79
    }
91✔
80

81
    protected abstract readonly type?: LoggerType;
82

83
    protected abstract logCore(level: LogLevel, payload: LogPayload): void;
84

85
    private redactIfNecessary(message: string, noRedaction: LogPayload["noRedaction"]): string {
2✔
86
        if (typeof noRedaction === "boolean" && noRedaction) {
91✔
87
            // If the consumer has supplied noRedaction: true, we don't redact the log message
88
            // regardless of the logger type
89
            return message;
22✔
90
        }
22✔
91

92
        if (typeof noRedaction === "string" && noRedaction === this.type) {
91✔
93
            // If the consumer has supplied noRedaction: logger-type, we skip redacting if
94
            // our logger type is the same as what the consumer requested
95
            return message;
8✔
96
        }
8✔
97

98
        if (
61✔
99
            typeof noRedaction === "object" &&
61✔
100
            Array.isArray(noRedaction) &&
8✔
101
            this.type &&
8✔
102
            noRedaction.indexOf(this.type) !== -1
8✔
103
        ) {
91✔
104
            // If the consumer has supplied noRedaction: array, we skip redacting if our logger
105
            // type is included in that array
106
            return message;
6✔
107
        }
6✔
108

109
        return redact(message);
55✔
110
    }
91✔
111

112
    public info(payload: LogPayload): void {
2✔
113
        this.log("info", payload);
296✔
114
    }
296✔
115

116
    public error(payload: LogPayload): void {
2✔
117
        this.log("error", payload);
85✔
118
    }
85✔
119
    public debug(payload: LogPayload): void {
2✔
120
        this.log("debug", payload);
617✔
121
    }
617✔
122

123
    public notice(payload: LogPayload): void {
2✔
124
        this.log("notice", payload);
4✔
125
    }
4✔
126

127
    public warning(payload: LogPayload): void {
2✔
128
        this.log("warning", payload);
7✔
129
    }
7✔
130

131
    public critical(payload: LogPayload): void {
2✔
132
        this.log("critical", payload);
×
133
    }
×
134

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

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

144
export class ConsoleLogger extends LoggerBase {
2✔
145
    protected readonly type: LoggerType = "console";
28✔
146

147
    protected logCore(level: LogLevel, payload: LogPayload): void {
2✔
148
        const { id, context, message } = payload;
37✔
149
        console.error(
37✔
150
            `[${level.toUpperCase()}] ${id.__value} - ${context}: ${message} (${process.pid}${this.serializeAttributes(payload.attributes)})`
37✔
151
        );
37✔
152
    }
37✔
153

154
    private serializeAttributes(attributes?: Record<string, string>): string {
2✔
155
        if (!attributes || Object.keys(attributes).length === 0) {
37✔
156
            return "";
20✔
157
        }
20✔
158
        return `, ${Object.entries(attributes)
17✔
159
            .map(([key, value]) => `${key}=${value}`)
17✔
160
            .join(", ")}`;
17✔
161
    }
37✔
162
}
2✔
163

164
export class DiskLogger extends LoggerBase<{ initialized: [] }> {
2✔
165
    private bufferedMessages: { level: LogLevel; payload: LogPayload }[] = [];
2✔
166
    private logWriter?: MongoLogWriter;
167

168
    public constructor(logPath: string, onError: (error: Error) => void) {
2✔
169
        super();
4✔
170

171
        void this.initialize(logPath, onError);
4✔
172
    }
4✔
173

174
    private async initialize(logPath: string, onError: (error: Error) => void): Promise<void> {
2✔
175
        try {
4✔
176
            await fs.mkdir(logPath, { recursive: true });
4✔
177

178
            const manager = new MongoLogManager({
4✔
179
                directory: logPath,
4✔
180
                retentionDays: 30,
4✔
181
                onwarn: console.warn,
4✔
182
                onerror: console.error,
4✔
183
                gzip: false,
4✔
184
                retentionGB: 1,
4✔
185
            });
4✔
186

187
            await manager.cleanupOldLogFiles();
4✔
188

189
            this.logWriter = await manager.createLogWriter();
4✔
190

191
            for (const message of this.bufferedMessages) {
4✔
192
                this.logCore(message.level, message.payload);
4✔
193
            }
4✔
194
            this.bufferedMessages = [];
4✔
195
            this.emit("initialized");
4✔
196
        } catch (error: unknown) {
4!
NEW
197
            onError(error as Error);
×
NEW
198
        }
×
199
    }
4✔
200

201
    protected type: LoggerType = "disk";
2✔
202

203
    protected logCore(level: LogLevel, payload: LogPayload): void {
2✔
204
        if (!this.logWriter) {
8✔
205
            // If the log writer is not initialized, buffer the message
206
            this.bufferedMessages.push({ level, payload });
4✔
207
            return;
4✔
208
        }
4✔
209

210
        const { id, context, message } = payload;
4✔
211
        const mongoDBLevel = this.mapToMongoDBLogLevel(level);
4✔
212

213
        this.logWriter[mongoDBLevel]("MONGODB-MCP", id, context, message, payload.attributes);
4✔
214
    }
8✔
215

216
    private mapToMongoDBLogLevel(level: LogLevel): "info" | "warn" | "error" | "debug" | "fatal" {
2✔
217
        switch (level) {
4✔
218
            case "info":
4✔
219
                return "info";
4✔
220
            case "warning":
4!
221
                return "warn";
×
222
            case "error":
4!
UNCOV
223
                return "error";
×
224
            case "notice":
4!
225
            case "debug":
4!
UNCOV
226
                return "debug";
×
227
            case "critical":
4!
228
            case "alert":
4!
229
            case "emergency":
4!
230
                return "fatal";
×
231
            default:
4!
232
                return "info";
×
233
        }
4✔
234
    }
4✔
235
}
2✔
236

237
export class McpLogger extends LoggerBase {
2✔
238
    public constructor(private readonly server: McpServer) {
2✔
239
        super();
26✔
240
    }
26✔
241

242
    protected readonly type: LoggerType = "mcp";
26✔
243

244
    protected logCore(level: LogLevel, payload: LogPayload): void {
2✔
245
        // Only log if the server is connected
246
        if (!this.server?.isConnected()) {
18!
UNCOV
247
            return;
×
UNCOV
248
        }
×
249

250
        void this.server.server.sendLoggingMessage({
18✔
251
            level,
18✔
252
            data: `[${payload.context}]: ${payload.message}`,
18✔
253
        });
18✔
254
    }
18✔
255
}
2✔
256

257
export class CompositeLogger extends LoggerBase {
2✔
258
    protected readonly type?: LoggerType;
259

260
    private readonly loggers: LoggerBase[] = [];
2✔
261
    private readonly attributes: Record<string, string> = {};
2✔
262

263
    constructor(...loggers: LoggerBase[]) {
2✔
264
        super();
92✔
265

266
        this.loggers = loggers;
92✔
267
    }
92✔
268

269
    public addLogger(logger: LoggerBase): void {
2✔
NEW
270
        this.loggers.push(logger);
×
UNCOV
271
    }
×
272

273
    public log(level: LogLevel, payload: LogPayload): void {
2✔
274
        // Override the public method to avoid the base logger redacting the message payload
275
        for (const logger of this.loggers) {
962✔
276
            logger.log(level, { ...payload, attributes: { ...this.attributes, ...payload.attributes } });
44✔
277
        }
44✔
278
    }
962✔
279

280
    protected logCore(): void {
2✔
281
        throw new Error("logCore should never be invoked on CompositeLogger");
×
282
    }
×
283

284
    public setAttribute(key: string, value: string): void {
2✔
285
        this.attributes[key] = value;
8✔
286
    }
8✔
287
}
2✔
288

289
export class NullLogger extends LoggerBase {
2✔
290
    protected type?: LoggerType;
291

292
    protected logCore(): void {
2✔
293
        // No-op logger, does not log anything
294
    }
32✔
295
}
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