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

mongodb-js / mongodb-mcp-server / 18007165187

25 Sep 2025 12:17PM UTC coverage: 82.532%. First build
18007165187

Pull #592

github

web-flow
Merge 91bffa85d into 91c99e595
Pull Request #592: chore: improve descriptions about connection - MCP-224

1092 of 1437 branches covered (75.99%)

Branch coverage included in aggregate %.

48 of 54 new or added lines in 3 files covered. (88.89%)

5296 of 6303 relevant lines covered (84.02%)

67.09 hits per line

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

80.36
/src/server.ts
1
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
import type { Session } from "./common/session.js";
3
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
4
import { AtlasTools } from "./tools/atlas/tools.js";
2✔
5
import { MongoDbTools } from "./tools/mongodb/tools.js";
2✔
6
import { Resources } from "./resources/resources.js";
2✔
7
import type { LogLevel } from "./common/logger.js";
8
import { LogId, McpLogger } from "./common/logger.js";
2✔
9
import type { Telemetry } from "./telemetry/telemetry.js";
10
import type { UserConfig } from "./common/config.js";
11
import { type ServerEvent } from "./telemetry/types.js";
12
import { type ServerCommand } from "./telemetry/types.js";
13
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
14
import {
2✔
15
    CallToolRequestSchema,
16
    SetLevelRequestSchema,
17
    SubscribeRequestSchema,
18
    UnsubscribeRequestSchema,
19
} from "@modelcontextprotocol/sdk/types.js";
20
import assert from "assert";
2✔
21
import type { ToolBase } from "./tools/tool.js";
22
import { validateConnectionString } from "./helpers/connectionOptions.js";
2✔
23
import { packageInfo } from "./common/packageInfo.js";
2✔
24
import { type ConnectionErrorHandler } from "./common/connectionErrorHandler.js";
25
import type { Elicitation } from "./elicitation.js";
26

27
export interface ServerOptions {
28
    session: Session;
29
    userConfig: UserConfig;
30
    mcpServer: McpServer;
31
    telemetry: Telemetry;
32
    elicitation: Elicitation;
33
    connectionErrorHandler: ConnectionErrorHandler;
34
}
35

36
export class Server {
2✔
37
    public readonly session: Session;
38
    public readonly mcpServer: McpServer;
39
    private readonly telemetry: Telemetry;
40
    public readonly userConfig: UserConfig;
41
    public readonly elicitation: Elicitation;
42
    public readonly tools: ToolBase[] = [];
2✔
43
    public readonly connectionErrorHandler: ConnectionErrorHandler;
44

45
    private _mcpLogLevel: LogLevel = "debug";
2✔
46

47
    public get mcpLogLevel(): LogLevel {
2✔
48
        return this._mcpLogLevel;
2✔
49
    }
2✔
50

51
    private readonly startTime: number;
52
    private readonly subscriptions = new Set<string>();
2✔
53

54
    constructor({ session, mcpServer, userConfig, telemetry, connectionErrorHandler, elicitation }: ServerOptions) {
2✔
55
        this.startTime = Date.now();
77✔
56
        this.session = session;
77✔
57
        this.telemetry = telemetry;
77✔
58
        this.mcpServer = mcpServer;
77✔
59
        this.userConfig = userConfig;
77✔
60
        this.elicitation = elicitation;
77✔
61
        this.connectionErrorHandler = connectionErrorHandler;
77✔
62
    }
77✔
63

64
    async connect(transport: Transport): Promise<void> {
2✔
65
        await this.validateConfig();
76✔
66
        // Register resources after the server is initialized so they can listen to events like
67
        // connection events.
68
        this.registerResources();
76✔
69
        this.mcpServer.server.registerCapabilities({
76✔
70
            logging: {},
76✔
71
            resources: { listChanged: true, subscribe: true },
76✔
72
            instructions: this.getInstructions(),
76✔
73
        });
76✔
74

75
        // TODO: Eventually we might want to make tools reactive too instead of relying on custom logic.
76
        this.registerTools();
76✔
77

78
        // This is a workaround for an issue we've seen with some models, where they'll see that everything in the `arguments`
79
        // object is optional, and then not pass it at all. However, the MCP server expects the `arguments` object to be if
80
        // the tool accepts any arguments, even if they're all optional.
81
        //
82
        // see: https://github.com/modelcontextprotocol/typescript-sdk/blob/131776764536b5fdca642df51230a3746fb4ade0/src/server/mcp.ts#L705
83
        // Since paramsSchema here is not undefined, the server will create a non-optional z.object from it.
84
        const existingHandler = (
76✔
85
            this.mcpServer.server["_requestHandlers"] as Map<
76✔
86
                string,
87
                (request: unknown, extra: unknown) => Promise<CallToolResult>
88
            >
89
        ).get(CallToolRequestSchema.shape.method.value);
76✔
90

91
        assert(existingHandler, "No existing handler found for CallToolRequestSchema");
76✔
92

93
        this.mcpServer.server.setRequestHandler(CallToolRequestSchema, (request, extra): Promise<CallToolResult> => {
76✔
94
            if (!request.params.arguments) {
573✔
95
                request.params.arguments = {};
1✔
96
            }
1✔
97

98
            return existingHandler(request, extra);
573✔
99
        });
76✔
100

101
        this.mcpServer.server.setRequestHandler(SubscribeRequestSchema, ({ params }) => {
76✔
102
            this.subscriptions.add(params.uri);
20✔
103
            this.session.logger.debug({
20✔
104
                id: LogId.serverInitialized,
20✔
105
                context: "resources",
20✔
106
                message: `Client subscribed to resource: ${params.uri}`,
20✔
107
            });
20✔
108
            return {};
20✔
109
        });
76✔
110

111
        this.mcpServer.server.setRequestHandler(UnsubscribeRequestSchema, ({ params }) => {
76✔
112
            this.subscriptions.delete(params.uri);
×
113
            this.session.logger.debug({
×
114
                id: LogId.serverInitialized,
×
115
                context: "resources",
×
116
                message: `Client unsubscribed from resource: ${params.uri}`,
×
117
            });
×
118
            return {};
×
119
        });
76✔
120

121
        this.mcpServer.server.setRequestHandler(SetLevelRequestSchema, ({ params }) => {
76✔
122
            if (!McpLogger.LOG_LEVELS.includes(params.level)) {
×
123
                throw new Error(`Invalid log level: ${params.level}`);
×
124
            }
×
125

126
            this._mcpLogLevel = params.level;
×
127
            return {};
×
128
        });
76✔
129

130
        this.mcpServer.server.oninitialized = (): void => {
76✔
131
            this.session.setMcpClient(this.mcpServer.server.getClientVersion());
76✔
132
            // Placed here to start the connection to the config connection string as soon as the server is initialized.
133
            void this.connectToConfigConnectionString();
76✔
134
            this.session.logger.info({
76✔
135
                id: LogId.serverInitialized,
76✔
136
                context: "server",
76✔
137
                message: `Server with version ${packageInfo.version} started with transport ${transport.constructor.name} and agent runner ${JSON.stringify(this.session.mcpClient)}`,
76✔
138
            });
76✔
139

140
            this.emitServerTelemetryEvent("start", Date.now() - this.startTime);
76✔
141
        };
76✔
142

143
        this.mcpServer.server.onclose = (): void => {
76✔
144
            const closeTime = Date.now();
76✔
145
            this.emitServerTelemetryEvent("stop", Date.now() - closeTime);
76✔
146
        };
76✔
147

148
        this.mcpServer.server.onerror = (error: Error): void => {
76✔
149
            const closeTime = Date.now();
×
NEW
150
            this.emitServerTelemetryEvent("stop", Date.now() - closeTime, error);
×
151
        };
×
152

153
        await this.mcpServer.connect(transport);
76✔
154
    }
76✔
155

156
    async close(): Promise<void> {
2✔
157
        await this.telemetry.close();
76✔
158
        await this.session.close();
76✔
159
        await this.mcpServer.close();
76✔
160
    }
76✔
161

162
    public sendResourceListChanged(): void {
2✔
163
        this.mcpServer.sendResourceListChanged();
628✔
164
    }
628✔
165

166
    public sendResourceUpdated(uri: string): void {
2✔
167
        this.session.logger.info({
628✔
168
            id: LogId.serverInitialized,
628✔
169
            context: "resources",
628✔
170
            message: `Resource updated: ${uri}`,
628✔
171
        });
628✔
172

173
        if (this.subscriptions.has(uri)) {
628!
174
            void this.mcpServer.server.sendResourceUpdated({ uri });
20✔
175
        }
20✔
176
    }
628✔
177

178
    private emitServerTelemetryEvent(command: ServerCommand, commandDuration: number, error?: Error): void {
2✔
179
        const event: ServerEvent = {
152✔
180
            timestamp: new Date().toISOString(),
152✔
181
            source: "mdbmcp",
152✔
182
            properties: {
152✔
183
                result: "success",
152✔
184
                duration_ms: commandDuration,
152✔
185
                component: "server",
152✔
186
                category: "other",
152✔
187
                command: command,
152✔
188
            },
152✔
189
        };
152✔
190

191
        if (command === "start") {
152✔
192
            event.properties.startup_time_ms = commandDuration;
76✔
193
            event.properties.read_only_mode = this.userConfig.readOnly || false;
76✔
194
            event.properties.disabled_tools = this.userConfig.disabledTools || [];
76!
195
            event.properties.confirmation_required_tools = this.userConfig.confirmationRequiredTools || [];
76!
196
        }
76✔
197
        if (command === "stop") {
152✔
198
            event.properties.runtime_duration_ms = Date.now() - this.startTime;
76✔
199
            if (error) {
76!
200
                event.properties.result = "failure";
×
201
                event.properties.reason = error.message;
×
202
            }
×
203
        }
76✔
204

205
        this.telemetry.emitEvents([event]);
152✔
206
    }
152✔
207

208
    private registerTools(): void {
2✔
209
        for (const toolConstructor of [...AtlasTools, ...MongoDbTools]) {
76✔
210
            const tool = new toolConstructor({
2,508✔
211
                session: this.session,
2,508✔
212
                config: this.userConfig,
2,508✔
213
                telemetry: this.telemetry,
2,508✔
214
                elicitation: this.elicitation,
2,508✔
215
            });
2,508✔
216
            if (tool.register(this)) {
2,508✔
217
                this.tools.push(tool);
1,739✔
218
            }
1,739✔
219
        }
2,508✔
220
    }
76✔
221

222
    private registerResources(): void {
2✔
223
        for (const resourceConstructor of Resources) {
76✔
224
            const resource = new resourceConstructor(this.session, this.userConfig, this.telemetry);
228✔
225
            resource.register(this);
228✔
226
        }
228✔
227
    }
76✔
228

229
    private async validateConfig(): Promise<void> {
2✔
230
        // Validate connection string
231
        if (this.userConfig.connectionString) {
76!
232
            try {
5✔
233
                validateConnectionString(this.userConfig.connectionString, false);
5✔
234
            } catch (error) {
5!
235
                console.error("Connection string validation failed with error: ", error);
×
236
                throw new Error(
×
237
                    "Connection string validation failed with error: " +
×
238
                        (error instanceof Error ? error.message : String(error))
×
239
                );
×
240
            }
×
241
        }
5✔
242

243
        // Validate API client credentials
244
        if (this.userConfig.apiClientId && this.userConfig.apiClientSecret) {
76✔
245
            try {
13✔
246
                if (!this.userConfig.apiBaseUrl.startsWith("https://")) {
13!
NEW
247
                    const message =
×
NEW
248
                        "Failed to validate MongoDB Atlas the credentials from config: apiBaseUrl must start with https://";
×
NEW
249
                    console.error(message);
×
NEW
250
                    throw new Error(message);
×
NEW
251
                }
×
252

253
                await this.session.apiClient.validateAccessToken();
13✔
254
            } catch (error) {
13!
255
                if (this.userConfig.connectionString === undefined) {
×
256
                    console.error("Failed to validate MongoDB Atlas the credentials from the config: ", error);
×
257

258
                    throw new Error(
×
259
                        "Failed to connect to MongoDB Atlas instance using the credentials from the config"
×
260
                    );
×
261
                }
×
262
                console.error(
×
263
                    "Failed to validate MongoDB Atlas the credentials from the config, but validated the connection string."
×
264
                );
×
265
            }
×
266
        }
13✔
267
    }
76✔
268

269
    private getInstructions(): string {
2✔
270
        let instructions = `
76✔
271
            This is the MongoDB MCP server.
272
        `;
273
        if (this.userConfig.connectionString) {
76!
274
            instructions = `
5✔
275
            This MCP server was configured with a MongoDB connection string, and you can assume that you are connected to a MongoDB cluster.
276
            `;
277
        }
5✔
278

279
        if (this.userConfig.apiClientId && this.userConfig.apiClientSecret) {
76✔
280
            instructions = `
13✔
281
            This MCP server was configured with MongoDB Atlas API credentials.`;
282
        }
13✔
283

284
        return instructions;
76✔
285
    }
76✔
286

287
    private async connectToConfigConnectionString(): Promise<void> {
2✔
288
        if (this.userConfig.connectionString) {
76!
289
            try {
5✔
290
                this.session.logger.info({
5✔
291
                    id: LogId.mongodbConnectTry,
5✔
292
                    context: "server",
5✔
293
                    message: `Detected a MongoDB connection string in the configuration, trying to connect...`,
5✔
294
                });
5✔
295
                await this.session.connectToMongoDB({
5✔
296
                    connectionString: this.userConfig.connectionString,
5✔
297
                });
5✔
298
            } catch (error) {
3✔
299
                // We don't throw an error here because we want to allow the server to start even if the connection string is invalid.
300
                this.session.logger.error({
2✔
301
                    id: LogId.mongodbConnectFailure,
2✔
302
                    context: "server",
2✔
303
                    message: `Failed to connect to MongoDB instance using the connection string from the config: ${error instanceof Error ? error.message : String(error)}`,
2!
304
                });
2✔
305
            }
2✔
306
        }
5✔
307
    }
76✔
308
}
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

© 2026 Coveralls, Inc