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

mongodb-js / mongodb-mcp-server / 16779406063

06 Aug 2025 02:11PM UTC coverage: 81.424% (-0.4%) from 81.781%
16779406063

Pull #420

github

web-flow
Merge cc01ac54e into a35d18dd6
Pull Request #420: chore: allow logging unredacted messages MCP-103

679 of 874 branches covered (77.69%)

Branch coverage included in aggregate %.

161 of 291 new or added lines in 16 files covered. (55.33%)

7 existing lines in 4 files now uncovered.

3485 of 4240 relevant lines covered (82.19%)

63.42 hits per line

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

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

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

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

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

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

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

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

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

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

53
interface LogPayload {
54
    id: MongoLogId;
55
    context: string;
56
    message: string;
57
    noRedaction?: boolean | LoggerType | LoggerType[];
58
}
59

60
export type LoggerType = "console" | "disk" | "mcp";
61

62
export abstract class LoggerBase {
2✔
63
    private defaultUnredactedLogger: LoggerType = "mcp";
270✔
64

65
    public log(level: LogLevel, payload: LogPayload): void {
2✔
66
        // If no explicit value is supplied for unredacted loggers, default to "mcp"
67
        const noRedaction = payload.noRedaction !== undefined ? payload.noRedaction : this.defaultUnredactedLogger;
1,056✔
68

69
        this.logCore(level, {
1,056✔
70
            ...payload,
1,056✔
71
            message: this.redactIfNecessary(payload.message, noRedaction),
1,056✔
72
        });
1,056✔
73
    }
1,056✔
74

75
    protected abstract type: LoggerType;
76

77
    protected abstract logCore(level: LogLevel, payload: LogPayload): void;
78

79
    private redactIfNecessary(message: string, noRedaction: LogPayload["noRedaction"]): string {
2✔
80
        if (typeof noRedaction === "boolean" && noRedaction) {
1,056✔
81
            // If the consumer has supplied noRedaction: true, we don't redact the log message
82
            // regardless of the logger type
83
            return message;
804✔
84
        }
804✔
85

86
        if (typeof noRedaction === "string" && noRedaction === this.type) {
1,056✔
87
            // If the consumer has supplied noRedaction: logger-type, we skip redacting if
88
            // our logger type is the same as what the consumer requested
89
            return message;
12✔
90
        }
12✔
91

92
        if (
240✔
93
            typeof noRedaction === "object" &&
240✔
94
            Array.isArray(noRedaction) &&
8✔
95
            this.type !== null &&
8✔
96
            noRedaction.indexOf(this.type) !== -1
8✔
97
        ) {
1,056✔
98
            // If the consumer has supplied noRedaction: array, we skip redacting if our logger
99
            // type is included in that array
100
            return message;
6✔
101
        }
6✔
102

103
        return redact(message);
234✔
104
    }
1,056✔
105

106
    info(payload: LogPayload): void {
2✔
107
        this.log("info", payload);
286✔
108
    }
286✔
109

110
    error(payload: LogPayload): void {
2✔
111
        this.log("error", payload);
83✔
112
    }
83✔
113
    debug(payload: LogPayload): void {
2✔
114
        this.log("debug", payload);
637✔
115
    }
637✔
116

117
    notice(payload: LogPayload): void {
2✔
118
        this.log("notice", payload);
4✔
119
    }
4✔
120

121
    warning(payload: LogPayload): void {
2✔
122
        this.log("warning", payload);
7✔
123
    }
7✔
124

125
    critical(payload: LogPayload): void {
2✔
NEW
126
        this.log("critical", payload);
×
UNCOV
127
    }
×
128

129
    alert(payload: LogPayload): void {
2✔
NEW
130
        this.log("alert", payload);
×
UNCOV
131
    }
×
132

133
    emergency(payload: LogPayload): void {
2✔
NEW
134
        this.log("emergency", payload);
×
UNCOV
135
    }
×
136
}
2✔
137

138
export class ConsoleLogger extends LoggerBase {
2✔
139
    protected type: LoggerType = "console";
160✔
140

141
    protected logCore(level: LogLevel, payload: LogPayload): void {
2✔
142
        const { id, context, message } = payload;
972✔
143
        console.error(`[${level.toUpperCase()}] ${id.__value} - ${context}: ${message} (${process.pid})`);
972✔
144
    }
972✔
145
}
2✔
146

147
export class DiskLogger extends LoggerBase {
2✔
148
    private constructor(private logWriter: MongoLogWriter) {
2✔
149
        super();
4✔
150
    }
4✔
151

152
    protected type: LoggerType = "disk";
4✔
153

154
    static async fromPath(logPath: string): Promise<DiskLogger> {
2✔
155
        await fs.mkdir(logPath, { recursive: true });
4✔
156

157
        const manager = new MongoLogManager({
4✔
158
            directory: logPath,
4✔
159
            retentionDays: 30,
4✔
160
            onwarn: console.warn,
4✔
161
            onerror: console.error,
4✔
162
            gzip: false,
4✔
163
            retentionGB: 1,
4✔
164
        });
4✔
165

166
        await manager.cleanupOldLogFiles();
4✔
167

168
        const logWriter = await manager.createLogWriter();
4✔
169

170
        return new DiskLogger(logWriter);
4✔
171
    }
4✔
172

173
    protected logCore(level: LogLevel, payload: LogPayload): void {
2✔
174
        const { id, context, message } = payload;
35✔
175
        const mongoDBLevel = this.mapToMongoDBLogLevel(level);
35✔
176

177
        this.logWriter[mongoDBLevel]("MONGODB-MCP", id, context, message);
35✔
178
    }
35✔
179

180
    private mapToMongoDBLogLevel(level: LogLevel): "info" | "warn" | "error" | "debug" | "fatal" {
2✔
181
        switch (level) {
35✔
182
            case "info":
35✔
183
                return "info";
16✔
184
            case "warning":
35!
185
                return "warn";
×
186
            case "error":
35✔
187
                return "error";
4✔
188
            case "notice":
35!
189
            case "debug":
35✔
190
                return "debug";
15✔
191
            case "critical":
35!
192
            case "alert":
35!
193
            case "emergency":
35!
194
                return "fatal";
×
195
            default:
35!
196
                return "info";
×
197
        }
35✔
198
    }
35✔
199
}
2✔
200

201
export class McpLogger extends LoggerBase {
2✔
202
    constructor(private server: McpServer) {
2✔
203
        super();
22✔
204
    }
22✔
205

206
    type: LoggerType = "mcp";
22✔
207

208
    protected logCore(level: LogLevel, payload: LogPayload): void {
2✔
209
        // Only log if the server is connected
210
        if (!this.server?.isConnected()) {
49✔
211
            return;
7✔
212
        }
7✔
213

214
        void this.server.server.sendLoggingMessage({
42✔
215
            level,
42✔
216
            data: `[${payload.context}]: ${payload.message}`,
42✔
217
        });
42✔
218
    }
49✔
219
}
2✔
220

221
export class CompositeLogger extends LoggerBase {
2✔
222
    // This is not a real logger type - it should not be used anyway.
223
    protected type: LoggerType = "composite" as unknown as LoggerType;
2✔
224

225
    private loggers: LoggerBase[] = [];
2✔
226

227
    constructor(...loggers: LoggerBase[]) {
2✔
228
        super();
84✔
229

230
        this.setLoggers(...loggers);
84✔
231
    }
84✔
232

233
    setLoggers(...loggers: LoggerBase[]): void {
2✔
234
        if (loggers.length === 0) {
152!
235
            throw new Error("At least one logger must be provided");
×
236
        }
×
237
        this.loggers = [...loggers];
152✔
238
    }
152✔
239

240
    public log(level: LogLevel, payload: LogPayload): void {
2✔
241
        // Override the public method to avoid the base logger redacting the message payload
242
        for (const logger of this.loggers) {
997✔
243
            logger.log(level, payload);
1,036✔
244
        }
1,036✔
245
    }
997✔
246

247
    protected logCore(): void {
2✔
NEW
248
        throw new Error("logCore should never be invoked on CompositeLogger");
×
NEW
249
    }
×
250
}
2✔
251

252
const logger = new CompositeLogger(new ConsoleLogger());
2✔
253
export default logger;
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