Skip to content

Commit 42b03b0

Browse files
committed
Add mcp logging
1 parent c31333f commit 42b03b0

File tree

5 files changed

+115
-48
lines changed

5 files changed

+115
-48
lines changed

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
22
import { Server } from "./server.js";
3-
import log from "./logger.js";
3+
import logger from "./logger.js";
44
import { mongoLogId } from "mongodb-log-writer";
55

66
async function runServer() {
@@ -11,7 +11,7 @@ async function runServer() {
1111
}
1212

1313
runServer().catch((error) => {
14-
log.fatal(mongoLogId(1_000_004), "server", `Fatal error running server: ${error}`);
14+
logger.emergency(mongoLogId(1_000_004), "server", `Fatal error running server: ${error}`);
1515

1616
process.exit(1);
1717
});

src/logger.ts

Lines changed: 97 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,103 @@ import path from "path";
33
import config from "./config.js";
44
import redact from "mongodb-redact";
55
import fs from "fs/promises";
6+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7+
import { LoggingMessageNotification } from "@modelcontextprotocol/sdk/types.js";
68

7-
let logWriter: MongoLogWriter | undefined = undefined;
9+
export type LogLevel = LoggingMessageNotification["params"]["level"];
810

9-
export async function initializeLogger(): Promise<void> {
11+
abstract class LoggerBase {
12+
abstract log(level: LogLevel, id: MongoLogId, context: string, message: string): void;
13+
info(id: MongoLogId, context: string, message: string): void {
14+
this.log("info", id, context, message);
15+
}
16+
17+
error(id: MongoLogId, context: string, message: string): void {
18+
this.log("error", id, context, message);
19+
}
20+
debug(id: MongoLogId, context: string, message: string): void {
21+
this.log("debug", id, context, message);
22+
}
23+
24+
notice(id: MongoLogId, context: string, message: string): void {
25+
this.log("notice", id, context, message);
26+
}
27+
28+
warning(id: MongoLogId, context: string, message: string): void {
29+
this.log("warning", id, context, message);
30+
}
31+
32+
critical(id: MongoLogId, context: string, message: string): void {
33+
this.log("critical", id, context, message);
34+
}
35+
36+
alert(id: MongoLogId, context: string, message: string): void {
37+
this.log("alert", id, context, message);
38+
}
39+
40+
emergency(id: MongoLogId, context: string, message: string): void {
41+
this.log("emergency", id, context, message);
42+
}
43+
}
44+
45+
class ConsoleLogger extends LoggerBase {
46+
log(level: LogLevel, id: MongoLogId, context: string, message: string): void {
47+
message = redact(message);
48+
console.error(`[${level.toUpperCase()}] ${id} - ${context}: ${message}`);
49+
}
50+
}
51+
52+
class Logger extends LoggerBase {
53+
constructor(
54+
private logWriter: MongoLogWriter,
55+
private server: McpServer
56+
) {
57+
super();
58+
}
59+
60+
log(level: LogLevel, id: MongoLogId, context: string, message: string): void {
61+
message = redact(message);
62+
const mongoDBLevel = this.mapToMongoDBLogLevel(level);
63+
this.logWriter[mongoDBLevel]("MONGODB-MCP", id, context, message);
64+
this.server.server.sendLoggingMessage({
65+
level,
66+
data: `[${context}]: ${message}`,
67+
});
68+
}
69+
70+
private mapToMongoDBLogLevel(level: LogLevel): "info" | "warn" | "error" | "debug" | "fatal" {
71+
switch (level) {
72+
case "info":
73+
return "info";
74+
case "warning":
75+
return "warn";
76+
case "error":
77+
return "error";
78+
case "notice":
79+
case "debug":
80+
return "debug";
81+
case "critical":
82+
case "alert":
83+
case "emergency":
84+
return "fatal";
85+
default:
86+
return "info";
87+
}
88+
}
89+
}
90+
91+
class ProxyingLogger extends LoggerBase {
92+
private internalLogger: LoggerBase = new ConsoleLogger();
93+
94+
log(level: LogLevel, id: MongoLogId, context: string, message: string): void {
95+
this.internalLogger.log(level, id, context, message);
96+
}
97+
}
98+
99+
const logger = new ProxyingLogger();
100+
export default logger;
101+
102+
export async function initializeLogger(server: McpServer): Promise<void> {
10103
const logDir = path.join(config.localDataPath, ".app-logs");
11104
await fs.mkdir(logDir, { recursive: true });
12105

@@ -21,37 +114,6 @@ export async function initializeLogger(): Promise<void> {
21114

22115
await manager.cleanupOldLogFiles();
23116

24-
logWriter = await manager.createLogWriter();
117+
const logWriter = await manager.createLogWriter();
118+
logger["internalLogger"] = new Logger(logWriter, server);
25119
}
26-
27-
const log = {
28-
log(level: "info" | "warn" | "error" | "debug" | "fatal", id: MongoLogId, context: string, message: string): void {
29-
message = redact(message);
30-
if (logWriter) {
31-
logWriter[level]("MONGODB-MCP", id, context, message);
32-
} else {
33-
console.error(
34-
`[${level.toUpperCase()}] Logger not initialized, dropping message: ${message}, context: ${context}, id: ${id}`
35-
);
36-
}
37-
},
38-
39-
info(id: MongoLogId, context: string, message: string): void {
40-
this.log("info", id, context, message);
41-
},
42-
43-
warn(id: MongoLogId, context: string, message: string): void {
44-
this.log("warn", id, context, message);
45-
},
46-
error(id: MongoLogId, context: string, message: string): void {
47-
this.log("error", id, context, message);
48-
},
49-
debug(id: MongoLogId, context: string, message: string): void {
50-
this.log("debug", id, context, message);
51-
},
52-
fatal(id: MongoLogId, context: string, message: string): void {
53-
this.log("fatal", id, context, message);
54-
},
55-
};
56-
57-
export default log;

src/server.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
55
import { registerAtlasTools } from "./tools/atlas/tools.js";
66
import { registerMongoDBTools } from "./tools/mongodb/index.js";
77
import { config } from "./config.js";
8-
import log, { initializeLogger } from "./logger.js";
8+
import logger, { initializeLogger } from "./logger.js";
99
import { mongoLogId } from "mongodb-log-writer";
1010

1111
export class Server {
@@ -32,8 +32,6 @@ export class Server {
3232
},
3333
});
3434

35-
await initializeLogger();
36-
3735
this.initialized = true;
3836
}
3937

@@ -43,6 +41,8 @@ export class Server {
4341
version: config.version,
4442
});
4543

44+
server.server.registerCapabilities({ logging: {} });
45+
4646
registerAtlasTools(server, this.state!, this.apiClient!);
4747
registerMongoDBTools(server, this.state!);
4848

@@ -53,7 +53,8 @@ export class Server {
5353
await this.init();
5454
const server = this.createMcpServer();
5555
await server.connect(transport);
56+
await initializeLogger(server);
5657

57-
log.info(mongoLogId(1_000_004), "server", `Server started with transport ${transport.constructor.name}`);
58+
logger.info(mongoLogId(1_000_004), "server", `Server started with transport ${transport.constructor.name}`);
5859
}
5960
}

src/tools/atlas/auth.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
22
import { saveState } from "../../state.js";
33
import { AtlasToolBase } from "./atlasTool.js";
44
import { isAuthenticated } from "../../common/atlas/auth.js";
5-
import log from "../../logger.js";
5+
import logger from "../../logger.js";
66
import { mongoLogId } from "mongodb-log-writer";
77

88
export class AuthTool extends AtlasToolBase {
@@ -16,7 +16,7 @@ export class AuthTool extends AtlasToolBase {
1616

1717
async execute(): Promise<CallToolResult> {
1818
if (await this.isAuthenticated()) {
19-
log.debug(mongoLogId(1_000_001), "auth", "Already authenticated!");
19+
logger.debug(mongoLogId(1_000_001), "auth", "Already authenticated!");
2020
return {
2121
content: [{ type: "text", text: "You are already authenticated!" }],
2222
};
@@ -41,13 +41,13 @@ export class AuthTool extends AtlasToolBase {
4141
};
4242
} catch (error: unknown) {
4343
if (error instanceof Error) {
44-
log.error(mongoLogId(1_000_002), "auth", `Authentication error: ${error}`);
44+
logger.error(mongoLogId(1_000_002), "auth", `Authentication error: ${error}`);
4545
return {
4646
content: [{ type: "text", text: `Authentication failed: ${error.message}` }],
4747
};
4848
}
4949

50-
log.error(mongoLogId(1_000_003), "auth", `Unknown authentication error: ${error}`);
50+
logger.error(mongoLogId(1_000_003), "auth", `Unknown authentication error: ${error}`);
5151
return {
5252
content: [{ type: "text", text: "Authentication failed due to an unknown error." }],
5353
};

src/tools/tool.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
22
import { z, ZodNever, ZodRawShape } from "zod";
33
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
44
import { State } from "../state.js";
5-
import log from "../logger.js";
5+
import logger from "../logger.js";
66
import { mongoLogId } from "mongodb-log-writer";
77

88
export type ToolArgs<Args extends ZodRawShape> = z.objectOutputType<Args, ZodNever>;
@@ -22,11 +22,15 @@ export abstract class ToolBase {
2222
const callback = async (args: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> => {
2323
try {
2424
// TODO: add telemetry here
25-
log.debug(mongoLogId(1_000_006), "tool", `Executing ${this.name} with args: ${JSON.stringify(args)}`);
25+
logger.debug(
26+
mongoLogId(1_000_006),
27+
"tool",
28+
`Executing ${this.name} with args: ${JSON.stringify(args)}`
29+
);
2630

2731
return await this.execute(args);
2832
} catch (error) {
29-
log.error(mongoLogId(1_000_000), "tool", `Error executing ${this.name}: ${error}`);
33+
logger.error(mongoLogId(1_000_000), "tool", `Error executing ${this.name}: ${error}`);
3034

3135
// If the error is authentication related, suggest using auth tool
3236
if (error instanceof Error && error.message.includes("Not authenticated")) {

0 commit comments

Comments
 (0)