Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,4 @@ jobs:
rm -rf node_modules
npm pkg set scripts.prepare="exit 0"
npm install --omit=dev
- run: npx -y @modelcontextprotocol/[email protected].2 --cli --method tools/list -- node dist/esm/index.js
- run: npx -y @modelcontextprotocol/[email protected].5 --cli --method tools/list -- node dist/esm/index.js
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"@ai-sdk/google": "^1.2.22",
"@ai-sdk/openai": "^1.3.23",
"@eslint/js": "^9.30.1",
"@modelcontextprotocol/inspector": "^0.16.0",
"@modelcontextprotocol/inspector": "^0.16.5",
"@mongodb-js/oidc-mock-provider": "^0.11.3",
"@redocly/cli": "^2.0.7",
"@types/express": "^5.0.1",
Expand Down
26 changes: 22 additions & 4 deletions src/common/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import fs from "fs/promises";
import type { MongoLogId, MongoLogWriter } from "mongodb-log-writer";
import { mongoLogId, MongoLogManager } from "mongodb-log-writer";
import redact from "mongodb-redact";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import type { LoggingMessageNotification } from "@modelcontextprotocol/sdk/types.js";
import { EventEmitter } from "events";
import type { Server } from "../lib.js";

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

Expand Down Expand Up @@ -250,19 +250,37 @@ export class DiskLogger extends LoggerBase<{ initialized: [] }> {
}

export class McpLogger extends LoggerBase {
public constructor(private readonly server: McpServer) {
private static readonly LOG_LEVELS: LogLevel[] = [
"debug",
"info",
"notice",
"warning",
"error",
"critical",
"alert",
"emergency",
];

public constructor(private readonly server: Server) {
super();
}

protected readonly type: LoggerType = "mcp";

protected logCore(level: LogLevel, payload: LogPayload): void {
// Only log if the server is connected
if (!this.server?.isConnected()) {
if (!this.server.mcpServer.isConnected()) {
return;
}

void this.server.server.sendLoggingMessage({
const minimumLevel = McpLogger.LOG_LEVELS.indexOf(this.server.mcpLogLevel);
const currentLevel = McpLogger.LOG_LEVELS.indexOf(level);
Comment on lines +276 to +277
Copy link

Copilot AI Aug 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No validation for invalid log levels. If this.server.mcpLogLevel or level contains an invalid value not in LOG_LEVELS, indexOf() will return -1, leading to incorrect filtering behavior. Add validation to handle invalid log levels gracefully.

Suggested change
const minimumLevel = McpLogger.LOG_LEVELS.indexOf(this.server.mcpLogLevel);
const currentLevel = McpLogger.LOG_LEVELS.indexOf(level);
const currentLevel = McpLogger.LOG_LEVELS.indexOf(level);
if (minimumLevel === -1) {
// Invalid minimum log level configured; skip logging and optionally warn
console.warn(`[McpLogger] Invalid minimum log level: ${this.server.mcpLogLevel}`);
return;
}
if (currentLevel === -1) {
// Invalid log level requested; skip logging and optionally warn
console.warn(`[McpLogger] Invalid log level: ${level}`);
return;
}

Copilot uses AI. Check for mistakes.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a valid one.

if (minimumLevel > currentLevel) {
Comment on lines +276 to +278
Copy link

Copilot AI Aug 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comparison logic is inverted. If minimumLevel > currentLevel, it means the current level is actually lower priority than the minimum. The condition should be minimumLevel > currentLevel to filter out lower priority levels, but the comment says 'Don't log if the requested level is lower than the minimum level' which suggests the opposite logic. Please verify the intended behavior matches the LOG_LEVELS array ordering.

Copilot uses AI. Check for mistakes.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks wrong!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong as the copilot suggestion is wrong or the code itself?

// Don't log if the requested level is lower than the minimum level
return;
}

void this.server.mcpServer.server.sendLoggingMessage({
level,
data: `[${payload.context}]: ${payload.message}`,
});
Expand Down
14 changes: 14 additions & 0 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
import { AtlasTools } from "./tools/atlas/tools.js";
import { MongoDbTools } from "./tools/mongodb/tools.js";
import { Resources } from "./resources/resources.js";
import type { LogLevel } from "./common/logger.js";
import { LogId } from "./common/logger.js";
import type { Telemetry } from "./telemetry/telemetry.js";
import type { UserConfig } from "./common/config.js";
Expand All @@ -12,6 +13,7 @@ import { type ServerCommand } from "./telemetry/types.js";
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import {
CallToolRequestSchema,
SetLevelRequestSchema,
SubscribeRequestSchema,
UnsubscribeRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
Expand All @@ -32,6 +34,13 @@ export class Server {
private readonly telemetry: Telemetry;
public readonly userConfig: UserConfig;
public readonly tools: ToolBase[] = [];

private _mcpLogLevel: LogLevel = "debug";

public get mcpLogLevel(): LogLevel {
return this._mcpLogLevel;
}

private readonly startTime: number;
private readonly subscriptions = new Set<string>();

Expand Down Expand Up @@ -97,6 +106,11 @@ export class Server {
return {};
});

this.mcpServer.server.setRequestHandler(SetLevelRequestSchema, ({ params }) => {
this._mcpLogLevel = params.level;
return {};
});

this.mcpServer.server.oninitialized = (): void => {
this.session.setMcpClient(this.mcpServer.server.getClientVersion());
// Placed here to start the connection to the config connection string as soon as the server is initialized.
Expand Down
17 changes: 10 additions & 7 deletions src/transports/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,7 @@ export abstract class TransportRunnerBase {
version: packageInfo.version,
});

const loggers = [this.logger];
if (this.userConfig.loggers.includes("mcp")) {
loggers.push(new McpLogger(mcpServer));
}

const logger = new CompositeLogger(...loggers);
const logger = new CompositeLogger(this.logger);
const exportsManager = ExportsManager.init(this.userConfig, logger);
const connectionManager = new ConnectionManager(this.userConfig, this.driverOptions, logger, this.deviceId);

Expand All @@ -64,12 +59,20 @@ export abstract class TransportRunnerBase {

const telemetry = Telemetry.create(session, this.userConfig, this.deviceId);

return new Server({
const result = new Server({
mcpServer,
session,
telemetry,
userConfig: this.userConfig,
});

// We need to create the MCP logger after the server is constructed
// because it needs the server instance
if (this.userConfig.loggers.includes("mcp")) {
logger.addLogger(new McpLogger(result));
}

return result;
}

abstract start(): Promise<void>;
Expand Down
39 changes: 33 additions & 6 deletions tests/unit/logger.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import type { MockInstance } from "vitest";
import { describe, beforeEach, afterEach, vi, it, expect } from "vitest";
import type { LoggerType } from "../../src/common/logger.js";
import type { LoggerType, LogLevel } from "../../src/common/logger.js";
import { CompositeLogger, ConsoleLogger, DiskLogger, LogId, McpLogger } from "../../src/common/logger.js";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import os from "os";
import * as path from "path";
import * as fs from "fs/promises";
import { once } from "events";
import type { Server } from "../../src/server.js";

describe("Logger", () => {
let consoleErrorSpy: MockInstance<typeof console.error>;
let consoleLogger: ConsoleLogger;

let mcpLoggerSpy: MockInstance;
let mcpLogger: McpLogger;
let minimumMcpLogLevel: LogLevel;

beforeEach(() => {
// Mock console.error before creating the ConsoleLogger
Expand All @@ -22,12 +23,18 @@ describe("Logger", () => {
consoleLogger = new ConsoleLogger();

mcpLoggerSpy = vi.fn();
minimumMcpLogLevel = "debug";
mcpLogger = new McpLogger({
server: {
sendLoggingMessage: mcpLoggerSpy,
mcpServer: {
server: {
sendLoggingMessage: mcpLoggerSpy,
},
isConnected: () => true,
},
isConnected: () => true,
} as unknown as McpServer);
get mcpLogLevel() {
return minimumMcpLogLevel;
},
} as unknown as Server);
});

afterEach(() => {
Expand Down Expand Up @@ -306,4 +313,24 @@ describe("Logger", () => {
});
});
});

describe("mcp logger", () => {
it("filters out messages below the minimum log level", () => {
minimumMcpLogLevel = "debug";
mcpLogger.log("debug", { id: LogId.serverInitialized, context: "test", message: "Debug message" });

expect(mcpLoggerSpy).toHaveBeenCalledOnce();
expect(getLastMcpLogMessage()).toContain("Debug message");

minimumMcpLogLevel = "info";
mcpLogger.log("debug", { id: LogId.serverInitialized, context: "test", message: "Debug message 2" });

expect(mcpLoggerSpy).toHaveBeenCalledTimes(1);

mcpLogger.log("alert", { id: LogId.serverInitialized, context: "test", message: "Alert message" });

expect(mcpLoggerSpy).toHaveBeenCalledTimes(2);
expect(getLastMcpLogMessage()).toContain("Alert message");
});
});
});
Loading