Skip to content

Commit c31333f

Browse files
committed
feat: add logging
1 parent aad8b01 commit c31333f

File tree

9 files changed

+104
-13
lines changed

9 files changed

+104
-13
lines changed

package-lock.json

Lines changed: 8 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
"@types/express": "^5.0.1",
5656
"bson": "^6.10.3",
5757
"mongodb": "^6.15.0",
58+
"mongodb-log-writer": "^2.4.1",
59+
"mongodb-redact": "^1.1.6",
5860
"mongodb-schema": "^12.6.2",
5961
"zod": "^3.24.2"
6062
},

src/config.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import path from "path";
22
import fs from "fs";
33
import { fileURLToPath } from "url";
4+
import os from "os";
45

56
const __filename = fileURLToPath(import.meta.url);
67
const __dirname = path.dirname(__filename);
@@ -15,6 +16,19 @@ export const config = {
1516
stateFile: process.env.STATE_FILE || path.resolve("./state.json"),
1617
projectID: process.env.PROJECT_ID,
1718
userAgent: `AtlasMCP/${packageJson.version} (${process.platform}; ${process.arch}; ${process.env.HOSTNAME || "unknown"})`,
19+
localDataPath: getLocalDataPath(),
1820
};
1921

2022
export default config;
23+
24+
function getLocalDataPath() {
25+
if (process.platform === "win32") {
26+
const appData = process.env.APPDATA;
27+
const localAppData = process.env.LOCALAPPDATA ?? process.env.APPDATA;
28+
if (localAppData && appData) {
29+
return path.join(localAppData, "mongodb", "mongodb-mcp");
30+
}
31+
}
32+
33+
return path.join(os.homedir(), ".mongodb", "mongodb-mcp");
34+
}

src/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
22
import { Server } from "./server.js";
3+
import log from "./logger.js";
4+
import { mongoLogId } from "mongodb-log-writer";
35

46
async function runServer() {
57
const server = new Server();
@@ -9,6 +11,7 @@ async function runServer() {
911
}
1012

1113
runServer().catch((error) => {
12-
console.error(`Fatal error running server:`, error);
14+
log.fatal(mongoLogId(1_000_004), "server", `Fatal error running server: ${error}`);
15+
1316
process.exit(1);
1417
});

src/logger.ts

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,57 @@
1-
// TODO: use a proper logger here
2-
export function log(level: string, message: string) {
3-
console.error(`[${level.toUpperCase()}] ${message}`);
1+
import { MongoLogId, MongoLogManager, MongoLogWriter } from "mongodb-log-writer";
2+
import path from "path";
3+
import config from "./config.js";
4+
import redact from "mongodb-redact";
5+
import fs from "fs/promises";
6+
7+
let logWriter: MongoLogWriter | undefined = undefined;
8+
9+
export async function initializeLogger(): Promise<void> {
10+
const logDir = path.join(config.localDataPath, ".app-logs");
11+
await fs.mkdir(logDir, { recursive: true });
12+
13+
const manager = new MongoLogManager({
14+
directory: path.join(config.localDataPath, ".app-logs"),
15+
retentionDays: 30,
16+
onwarn: console.warn,
17+
onerror: console.error,
18+
gzip: false,
19+
retentionGB: 1,
20+
});
21+
22+
await manager.cleanupOldLogFiles();
23+
24+
logWriter = await manager.createLogWriter();
425
}
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: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ 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";
9+
import { mongoLogId } from "mongodb-log-writer";
810

911
export class Server {
1012
state: State | undefined = undefined;
@@ -30,6 +32,8 @@ export class Server {
3032
},
3133
});
3234

35+
await initializeLogger();
36+
3337
this.initialized = true;
3438
}
3539

@@ -49,5 +53,7 @@ export class Server {
4953
await this.init();
5054
const server = this.createMcpServer();
5155
await server.connect(transport);
56+
57+
log.info(mongoLogId(1_000_004), "server", `Server started with transport ${transport.constructor.name}`);
5258
}
5359
}

src/state.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,12 @@ export interface State {
1111
}
1212

1313
export async function saveState(state: State): Promise<void> {
14+
const stateCopy: State = {
15+
auth: state.auth,
16+
};
17+
1418
return new Promise((resolve, reject) => {
15-
fs.writeFile(config.stateFile, JSON.stringify(state), function (err) {
19+
fs.writeFile(config.stateFile, JSON.stringify(stateCopy), function (err) {
1620
if (err) {
1721
return reject(err);
1822
}

src/tools/atlas/auth.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2-
import { log } from "../../logger.js";
32
import { saveState } from "../../state.js";
43
import { AtlasToolBase } from "./atlasTool.js";
54
import { isAuthenticated } from "../../common/atlas/auth.js";
5+
import log from "../../logger.js";
6+
import { mongoLogId } from "mongodb-log-writer";
67

78
export class AuthTool extends AtlasToolBase {
89
protected name = "atlas-auth";
@@ -15,7 +16,7 @@ export class AuthTool extends AtlasToolBase {
1516

1617
async execute(): Promise<CallToolResult> {
1718
if (await this.isAuthenticated()) {
18-
log("INFO", "Already authenticated!");
19+
log.debug(mongoLogId(1_000_001), "auth", "Already authenticated!");
1920
return {
2021
content: [{ type: "text", text: "You are already authenticated!" }],
2122
};
@@ -40,13 +41,13 @@ export class AuthTool extends AtlasToolBase {
4041
};
4142
} catch (error: unknown) {
4243
if (error instanceof Error) {
43-
log("error", `Authentication error: ${error}`);
44+
log.error(mongoLogId(1_000_002), "auth", `Authentication error: ${error}`);
4445
return {
4546
content: [{ type: "text", text: `Authentication failed: ${error.message}` }],
4647
};
4748
}
4849

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

src/tools/tool.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
22
import { z, ZodNever, ZodRawShape } from "zod";
3-
import { log } from "../logger.js";
43
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
54
import { State } from "../state.js";
5+
import log from "../logger.js";
6+
import { mongoLogId } from "mongodb-log-writer";
67

78
export type ToolArgs<Args extends ZodRawShape> = z.objectOutputType<Args, ZodNever>;
89

@@ -21,10 +22,11 @@ export abstract class ToolBase {
2122
const callback = async (args: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> => {
2223
try {
2324
// TODO: add telemetry here
25+
log.debug(mongoLogId(1_000_006), "tool", `Executing ${this.name} with args: ${JSON.stringify(args)}`);
2426

2527
return await this.execute(args);
2628
} catch (error) {
27-
log("error", `Error executing ${this.name}: ${error}`);
29+
log.error(mongoLogId(1_000_000), "tool", `Error executing ${this.name}: ${error}`);
2830

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

0 commit comments

Comments
 (0)