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

mongodb-js / mongodb-mcp-server / 18342272475

08 Oct 2025 10:52AM UTC coverage: 82.531% (+0.03%) from 82.505%
18342272475

Pull #621

github

web-flow
Merge 33ba4d126 into 543301c2d
Pull Request #621: feat: add ability to create vector search indexes MCP-234

1101 of 1447 branches covered (76.09%)

Branch coverage included in aggregate %.

63 of 73 new or added lines in 1 file covered. (86.3%)

37 existing lines in 2 files now uncovered.

5362 of 6384 relevant lines covered (83.99%)

67.49 hits per line

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

80.89
/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, ToolConstructorParams } 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
    toolConstructors?: (new (params: ToolConstructorParams) => ToolBase)[];
35
}
36

37
export class Server {
2✔
38
    public readonly session: Session;
39
    public readonly mcpServer: McpServer;
40
    private readonly telemetry: Telemetry;
41
    public readonly userConfig: UserConfig;
42
    public readonly elicitation: Elicitation;
43
    private readonly toolConstructors: (new (params: ToolConstructorParams) => ToolBase)[];
44
    public readonly tools: ToolBase[] = [];
2✔
45
    public readonly connectionErrorHandler: ConnectionErrorHandler;
46

47
    private _mcpLogLevel: LogLevel = "debug";
2✔
48

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

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

56
    constructor({
2✔
57
        session,
79✔
58
        mcpServer,
79✔
59
        userConfig,
79✔
60
        telemetry,
79✔
61
        connectionErrorHandler,
79✔
62
        elicitation,
79✔
63
        toolConstructors,
79✔
64
    }: ServerOptions) {
79✔
65
        this.startTime = Date.now();
79✔
66
        this.session = session;
79✔
67
        this.telemetry = telemetry;
79✔
68
        this.mcpServer = mcpServer;
79✔
69
        this.userConfig = userConfig;
79✔
70
        this.elicitation = elicitation;
79✔
71
        this.connectionErrorHandler = connectionErrorHandler;
79✔
72
        this.toolConstructors = toolConstructors ?? [...AtlasTools, ...MongoDbTools];
79✔
73
    }
79✔
74

75
    async connect(transport: Transport): Promise<void> {
2✔
76
        await this.validateConfig();
78✔
77
        // Register resources after the server is initialized so they can listen to events like
78
        // connection events.
79
        this.registerResources();
78✔
80
        this.mcpServer.server.registerCapabilities({
78✔
81
            logging: {},
78✔
82
            resources: { listChanged: true, subscribe: true },
78✔
83
            instructions: this.getInstructions(),
78✔
84
        });
78✔
85

86
        // TODO: Eventually we might want to make tools reactive too instead of relying on custom logic.
87
        this.registerTools();
78✔
88

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

102
        assert(existingHandler, "No existing handler found for CallToolRequestSchema");
78✔
103

104
        this.mcpServer.server.setRequestHandler(CallToolRequestSchema, (request, extra): Promise<CallToolResult> => {
78✔
105
            if (!request.params.arguments) {
573!
106
                request.params.arguments = {};
1✔
107
            }
1✔
108

109
            return existingHandler(request, extra);
573✔
110
        });
78✔
111

112
        this.mcpServer.server.setRequestHandler(SubscribeRequestSchema, ({ params }) => {
78✔
113
            this.subscriptions.add(params.uri);
20✔
114
            this.session.logger.debug({
20✔
115
                id: LogId.serverInitialized,
20✔
116
                context: "resources",
20✔
117
                message: `Client subscribed to resource: ${params.uri}`,
20✔
118
            });
20✔
119
            return {};
20✔
120
        });
78✔
121

122
        this.mcpServer.server.setRequestHandler(UnsubscribeRequestSchema, ({ params }) => {
78✔
123
            this.subscriptions.delete(params.uri);
×
124
            this.session.logger.debug({
×
UNCOV
125
                id: LogId.serverInitialized,
×
126
                context: "resources",
×
127
                message: `Client unsubscribed from resource: ${params.uri}`,
×
UNCOV
128
            });
×
UNCOV
129
            return {};
×
130
        });
78✔
131

132
        this.mcpServer.server.setRequestHandler(SetLevelRequestSchema, ({ params }) => {
78✔
UNCOV
133
            if (!McpLogger.LOG_LEVELS.includes(params.level)) {
×
UNCOV
134
                throw new Error(`Invalid log level: ${params.level}`);
×
UNCOV
135
            }
×
136

UNCOV
137
            this._mcpLogLevel = params.level;
×
UNCOV
138
            return {};
×
139
        });
78✔
140

141
        this.mcpServer.server.oninitialized = (): void => {
78✔
142
            this.session.setMcpClient(this.mcpServer.server.getClientVersion());
78✔
143
            // Placed here to start the connection to the config connection string as soon as the server is initialized.
144
            void this.connectToConfigConnectionString();
78✔
145
            this.session.logger.info({
78✔
146
                id: LogId.serverInitialized,
78✔
147
                context: "server",
78✔
148
                message: `Server with version ${packageInfo.version} started with transport ${transport.constructor.name} and agent runner ${JSON.stringify(this.session.mcpClient)}`,
78✔
149
            });
78✔
150

151
            this.emitServerTelemetryEvent("start", Date.now() - this.startTime);
78✔
152
        };
78✔
153

154
        this.mcpServer.server.onclose = (): void => {
78✔
155
            const closeTime = Date.now();
78✔
156
            this.emitServerTelemetryEvent("stop", Date.now() - closeTime);
78✔
157
        };
78✔
158

159
        this.mcpServer.server.onerror = (error: Error): void => {
78✔
UNCOV
160
            const closeTime = Date.now();
×
UNCOV
161
            this.emitServerTelemetryEvent("stop", Date.now() - closeTime, error);
×
UNCOV
162
        };
×
163

164
        await this.mcpServer.connect(transport);
78✔
165
    }
78✔
166

167
    async close(): Promise<void> {
2✔
168
        await this.telemetry.close();
78✔
169
        await this.session.close();
78✔
170
        await this.mcpServer.close();
78✔
171
    }
78✔
172

173
    public sendResourceListChanged(): void {
2✔
174
        this.mcpServer.sendResourceListChanged();
637✔
175
    }
637✔
176

177
    public sendResourceUpdated(uri: string): void {
2✔
178
        this.session.logger.info({
637✔
179
            id: LogId.resourceUpdateFailure,
637✔
180
            context: "resources",
637✔
181
            message: `Resource updated: ${uri}`,
637✔
182
        });
637✔
183

184
        if (this.subscriptions.has(uri)) {
637!
185
            void this.mcpServer.server.sendResourceUpdated({ uri });
20✔
186
        }
20✔
187
    }
637✔
188

189
    private emitServerTelemetryEvent(command: ServerCommand, commandDuration: number, error?: Error): void {
2✔
190
        const event: ServerEvent = {
156✔
191
            timestamp: new Date().toISOString(),
156✔
192
            source: "mdbmcp",
156✔
193
            properties: {
156✔
194
                result: "success",
156✔
195
                duration_ms: commandDuration,
156✔
196
                component: "server",
156✔
197
                category: "other",
156✔
198
                command: command,
156✔
199
            },
156✔
200
        };
156✔
201

202
        if (command === "start") {
156✔
203
            event.properties.startup_time_ms = commandDuration;
78✔
204
            event.properties.read_only_mode = this.userConfig.readOnly || false;
78✔
205
            event.properties.disabled_tools = this.userConfig.disabledTools || [];
78!
206
            event.properties.confirmation_required_tools = this.userConfig.confirmationRequiredTools || [];
78!
207
        }
78✔
208
        if (command === "stop") {
156✔
209
            event.properties.runtime_duration_ms = Date.now() - this.startTime;
78✔
210
            if (error) {
78!
UNCOV
211
                event.properties.result = "failure";
×
UNCOV
212
                event.properties.reason = error.message;
×
UNCOV
213
            }
×
214
        }
78✔
215

216
        this.telemetry.emitEvents([event]);
156✔
217
    }
156✔
218

219
    private registerTools(): void {
2✔
220
        for (const toolConstructor of this.toolConstructors) {
78✔
221
            const tool = new toolConstructor({
2,389✔
222
                session: this.session,
2,389✔
223
                config: this.userConfig,
2,389✔
224
                telemetry: this.telemetry,
2,389✔
225
                elicitation: this.elicitation,
2,389✔
226
            });
2,389✔
227
            if (tool.register(this)) {
2,389✔
228
                this.tools.push(tool);
1,775✔
229
            }
1,775✔
230
        }
2,389✔
231
    }
78✔
232

233
    private registerResources(): void {
2✔
234
        for (const resourceConstructor of Resources) {
78✔
235
            const resource = new resourceConstructor(this.session, this.userConfig, this.telemetry);
234✔
236
            resource.register(this);
234✔
237
        }
234✔
238
    }
78✔
239

240
    private async validateConfig(): Promise<void> {
2✔
241
        // Validate connection string
242
        if (this.userConfig.connectionString) {
78!
243
            try {
6✔
244
                validateConnectionString(this.userConfig.connectionString, false);
6✔
245
            } catch (error) {
6!
UNCOV
246
                console.error("Connection string validation failed with error: ", error);
×
247
                throw new Error(
×
248
                    "Connection string validation failed with error: " +
×
249
                        (error instanceof Error ? error.message : String(error))
×
250
                );
×
251
            }
×
252
        }
6✔
253

254
        // Validate API client credentials
255
        if (this.userConfig.apiClientId && this.userConfig.apiClientSecret) {
78✔
256
            try {
13✔
257
                if (!this.userConfig.apiBaseUrl.startsWith("https://")) {
13!
258
                    const message =
×
259
                        "Failed to validate MongoDB Atlas the credentials from config: apiBaseUrl must start with https://";
×
260
                    console.error(message);
×
261
                    throw new Error(message);
×
262
                }
×
263

264
                await this.session.apiClient.validateAccessToken();
13✔
265
            } catch (error) {
13!
UNCOV
266
                if (this.userConfig.connectionString === undefined) {
×
UNCOV
267
                    console.error("Failed to validate MongoDB Atlas the credentials from the config: ", error);
×
268

UNCOV
269
                    throw new Error(
×
UNCOV
270
                        "Failed to connect to MongoDB Atlas instance using the credentials from the config"
×
UNCOV
271
                    );
×
UNCOV
272
                }
×
UNCOV
273
                console.error(
×
UNCOV
274
                    "Failed to validate MongoDB Atlas the credentials from the config, but validated the connection string."
×
UNCOV
275
                );
×
UNCOV
276
            }
×
277
        }
13✔
278
    }
78✔
279

280
    private getInstructions(): string {
2✔
281
        let instructions = `
78✔
282
            This is the MongoDB MCP server.
283
        `;
284
        if (this.userConfig.connectionString) {
78!
285
            instructions += `
6✔
286
            This MCP server was configured with a MongoDB connection string, and you can assume that you are connected to a MongoDB cluster.
287
            `;
288
        }
6✔
289

290
        if (this.userConfig.apiClientId && this.userConfig.apiClientSecret) {
78✔
291
            instructions += `
13✔
292
            This MCP server was configured with MongoDB Atlas API credentials.`;
293
        }
13✔
294

295
        return instructions;
78✔
296
    }
78✔
297

298
    private async connectToConfigConnectionString(): Promise<void> {
2✔
299
        if (this.userConfig.connectionString) {
78!
300
            try {
6✔
301
                this.session.logger.info({
6✔
302
                    id: LogId.mongodbConnectTry,
6✔
303
                    context: "server",
6✔
304
                    message: `Detected a MongoDB connection string in the configuration, trying to connect...`,
6✔
305
                });
6✔
306
                await this.session.connectToMongoDB({
6✔
307
                    connectionString: this.userConfig.connectionString,
6✔
308
                });
6✔
309
            } catch (error) {
6✔
310
                // We don't throw an error here because we want to allow the server to start even if the connection string is invalid.
311
                this.session.logger.error({
2✔
312
                    id: LogId.mongodbConnectFailure,
2✔
313
                    context: "server",
2✔
314
                    message: `Failed to connect to MongoDB instance using the connection string from the config: ${error instanceof Error ? error.message : String(error)}`,
2!
315
                });
2✔
316
            }
2✔
317
        }
6✔
318
    }
78✔
319
}
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