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

mongodb-js / mongodb-mcp-server / 21475317403

29 Jan 2026 10:51AM UTC coverage: 82.229% (+0.6%) from 81.592%
21475317403

Pull #870

github

web-flow
Merge 8e0302c5f into 0c0f7290e
Pull Request #870: feat: allow session id to be passed externally MCP-369

1635 of 2100 branches covered (77.86%)

Branch coverage included in aggregate %.

115 of 165 new or added lines in 3 files covered. (69.7%)

78 existing lines in 7 files now uncovered.

7485 of 8991 relevant lines covered (83.25%)

118.15 hits per line

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

77.96
/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 { Resources } from "./resources/resources.js";
3✔
5
import type { LogLevel } from "./common/logger.js";
6
import { LogId, McpLogger } from "./common/logger.js";
3✔
7
import type { Telemetry } from "./telemetry/telemetry.js";
8
import type { UserConfig } from "./common/config/userConfig.js";
9
import { type ServerEvent } from "./telemetry/types.js";
10
import { type ServerCommand } from "./telemetry/types.js";
11
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
12
import {
3✔
13
    CallToolRequestSchema,
14
    SetLevelRequestSchema,
15
    SubscribeRequestSchema,
16
    UnsubscribeRequestSchema,
17
} from "@modelcontextprotocol/sdk/types.js";
18
import type { ToolBase, ToolCategory, ToolClass } from "./tools/tool.js";
19
import { validateConnectionString } from "./helpers/connectionOptions.js";
3✔
20
import { packageInfo } from "./common/packageInfo.js";
3✔
21
import { type ConnectionErrorHandler } from "./common/connectionErrorHandler.js";
22
import type { Elicitation } from "./elicitation.js";
23
import { AllTools } from "./tools/index.js";
3✔
24
import type { UIRegistry } from "./ui/registry/index.js";
25

26
export interface ServerOptions {
27
    session: Session;
28
    userConfig: UserConfig;
29
    mcpServer: McpServer;
30
    telemetry: Telemetry;
31
    elicitation: Elicitation;
32
    connectionErrorHandler: ConnectionErrorHandler;
33
    uiRegistry?: UIRegistry;
34
    /**
35
     * Custom tool constructors to register with the server.
36
     * This will override any default tools. You can use both existing and custom tools by using the `mongodb-mcp-server/tools` export.
37
     *
38
     * ```ts
39
     * import { AllTools, ToolBase, type ToolCategory, type OperationType } from "mongodb-mcp-server/tools";
40
     * class CustomTool extends ToolBase {
41
     *     override name = "custom_tool";
42
     *     static category: ToolCategory = "mongodb";
43
     *     static operationType: OperationType = "read";
44
     *     public description = "Custom tool description";
45
     *     public argsShape = {};
46
     *     protected async execute() {
47
     *         return { content: [{ type: "text", text: "Result" }] };
48
     *     }
49
     *     protected resolveTelemetryMetadata() {
50
     *         return {};
51
     *     }
52
     * }
53
     * const server = new Server({
54
     *     session: mySession,
55
     *     userConfig: myUserConfig,
56
     *     mcpServer: myMcpServer,
57
     *     telemetry: myTelemetry,
58
     *     elicitation: myElicitation,
59
     *     connectionErrorHandler: myConnectionErrorHandler,
60
     *     tools: [...AllTools, CustomTool],
61
     * });
62
     * ```
63
     */
64
    tools?: ToolClass[];
65
}
66

67
export class Server {
3✔
68
    public readonly session: Session;
69
    public readonly mcpServer: McpServer;
70
    private readonly telemetry: Telemetry;
71
    public readonly userConfig: UserConfig;
72
    public readonly elicitation: Elicitation;
73
    private readonly toolConstructors: ToolClass[];
74
    public readonly tools: ToolBase[] = [];
3✔
75
    public readonly connectionErrorHandler: ConnectionErrorHandler;
76
    public readonly uiRegistry?: UIRegistry;
77

78
    private _mcpLogLevel: LogLevel = "debug";
3✔
79

80
    public get mcpLogLevel(): LogLevel {
3✔
81
        return this._mcpLogLevel;
3✔
82
    }
3✔
83

84
    private readonly startTime: number;
85
    private readonly subscriptions = new Set<string>();
3✔
86

87
    constructor({
3✔
88
        session,
174✔
89
        mcpServer,
174✔
90
        userConfig,
174✔
91
        telemetry,
174✔
92
        connectionErrorHandler,
174✔
93
        elicitation,
174✔
94
        tools,
174✔
95
        uiRegistry,
174✔
96
    }: ServerOptions) {
174✔
97
        this.startTime = Date.now();
174✔
98
        this.session = session;
174✔
99
        this.telemetry = telemetry;
174✔
100
        this.mcpServer = mcpServer;
174✔
101
        this.userConfig = userConfig;
174✔
102
        this.elicitation = elicitation;
174✔
103
        this.connectionErrorHandler = connectionErrorHandler;
174✔
104
        this.toolConstructors = tools ?? AllTools;
174✔
105
        this.uiRegistry = uiRegistry;
174✔
106
    }
174✔
107

108
    async connect(transport: Transport): Promise<void> {
3✔
109
        await this.validateConfig();
165✔
110
        // Register resources after the server is initialized so they can listen to events like
111
        // connection events.
112
        this.registerResources();
165✔
113
        this.mcpServer.server.registerCapabilities({
165✔
114
            logging: {},
165✔
115
            resources: { listChanged: true, subscribe: true },
165✔
116
        });
165✔
117

118
        // TODO: Eventually we might want to make tools reactive too instead of relying on custom logic.
119
        this.registerTools();
165✔
120

121
        // This is a workaround for an issue we've seen with some models, where they'll see that everything in the `arguments`
122
        // object is optional, and then not pass it at all. However, the MCP server expects the `arguments` object to be if
123
        // the tool accepts any arguments, even if they're all optional.
124
        //
125
        // see: https://github.com/modelcontextprotocol/typescript-sdk/blob/131776764536b5fdca642df51230a3746fb4ade0/src/server/mcp.ts#L705
126
        // Since paramsSchema here is not undefined, the server will create a non-optional z.object from it.
127
        const existingHandler = (
165✔
128
            this.mcpServer.server["_requestHandlers"] as Map<
165✔
129
                string,
130
                (request: unknown, extra: unknown) => Promise<CallToolResult>
131
            >
132
        ).get(CallToolRequestSchema.shape.method.value);
165✔
133

134
        if (!existingHandler) {
165!
UNCOV
135
            throw new Error("No existing handler found for CallToolRequestSchema");
×
UNCOV
136
        }
✔
137

138
        this.mcpServer.server.setRequestHandler(CallToolRequestSchema, (request, extra): Promise<CallToolResult> => {
164✔
139
            if (!request.params.arguments) {
945!
140
                request.params.arguments = {};
1✔
141
            }
1✔
142

143
            return existingHandler(request, extra);
945✔
144
        });
164✔
145

146
        this.mcpServer.server.setRequestHandler(SubscribeRequestSchema, ({ params }) => {
164✔
147
            this.subscriptions.add(params.uri);
20✔
148
            this.session.logger.debug({
20✔
149
                id: LogId.serverInitialized,
20✔
150
                context: "resources",
20✔
151
                message: `Client subscribed to resource: ${params.uri}`,
20✔
152
            });
20✔
153
            return {};
20✔
154
        });
164✔
155

156
        this.mcpServer.server.setRequestHandler(UnsubscribeRequestSchema, ({ params }) => {
164✔
UNCOV
157
            this.subscriptions.delete(params.uri);
×
UNCOV
158
            this.session.logger.debug({
×
UNCOV
159
                id: LogId.serverInitialized,
×
UNCOV
160
                context: "resources",
×
UNCOV
161
                message: `Client unsubscribed from resource: ${params.uri}`,
×
UNCOV
162
            });
×
UNCOV
163
            return {};
×
164
        });
164✔
165

166
        this.mcpServer.server.setRequestHandler(SetLevelRequestSchema, ({ params }) => {
164✔
UNCOV
167
            if (!McpLogger.LOG_LEVELS.includes(params.level)) {
×
UNCOV
168
                throw new Error(`Invalid log level: ${params.level}`);
×
UNCOV
169
            }
×
170

UNCOV
171
            this._mcpLogLevel = params.level;
×
UNCOV
172
            return {};
×
173
        });
164✔
174

175
        this.mcpServer.server.oninitialized = (): void => {
164✔
176
            this.session.setMcpClient(this.mcpServer.server.getClientVersion());
145✔
177
            // Placed here to start the connection to the config connection string as soon as the server is initialized.
178
            void this.connectToConfigConnectionString();
145✔
179
            this.session.logger.info({
145✔
180
                id: LogId.serverInitialized,
145✔
181
                context: "server",
145✔
182
                message: `Server with version ${packageInfo.version} started with transport ${transport.constructor.name} and agent runner ${JSON.stringify(this.session.mcpClient)}`,
145✔
183
            });
145✔
184

185
            this.emitServerTelemetryEvent("start", Date.now() - this.startTime);
145✔
186
        };
145✔
187

188
        this.mcpServer.server.onclose = (): void => {
164✔
189
            const closeTime = Date.now();
160✔
190
            this.emitServerTelemetryEvent("stop", Date.now() - closeTime);
160✔
191
        };
160✔
192

193
        this.mcpServer.server.onerror = (error: Error): void => {
164✔
UNCOV
194
            const closeTime = Date.now();
×
UNCOV
195
            this.emitServerTelemetryEvent("stop", Date.now() - closeTime, error);
×
UNCOV
196
        };
×
197

198
        await this.mcpServer.connect(transport);
164✔
199
    }
165✔
200

201
    async close(): Promise<void> {
3✔
202
        await this.telemetry.close();
164✔
203
        await this.session.close();
164✔
204
        await this.mcpServer.close();
164✔
205
    }
164✔
206

207
    public sendResourceListChanged(): void {
3✔
208
        this.mcpServer.sendResourceListChanged();
1,041✔
209
    }
1,041✔
210

211
    public isToolCategoryAvailable(name: ToolCategory): boolean {
3✔
212
        return !!this.tools.filter((t) => t.category === name).length;
2✔
213
    }
2✔
214

215
    public sendResourceUpdated(uri: string): void {
3✔
216
        this.session.logger.info({
1,041✔
217
            id: LogId.resourceUpdateFailure,
1,041✔
218
            context: "resources",
1,041✔
219
            message: `Resource updated: ${uri}`,
1,041✔
220
        });
1,041✔
221

222
        if (this.subscriptions.has(uri)) {
1,041!
223
            void this.mcpServer.server.sendResourceUpdated({ uri });
20✔
224
        }
20✔
225
    }
1,041✔
226

227
    private emitServerTelemetryEvent(command: ServerCommand, commandDuration: number, error?: Error): void {
3✔
228
        const event: ServerEvent = {
305✔
229
            timestamp: new Date().toISOString(),
305✔
230
            source: "mdbmcp",
305✔
231
            properties: {
305✔
232
                result: "success",
305✔
233
                duration_ms: commandDuration,
305✔
234
                component: "server",
305✔
235
                category: "other",
305✔
236
                command: command,
305✔
237
            },
305✔
238
        };
305✔
239

240
        if (command === "start") {
305✔
241
            event.properties.startup_time_ms = commandDuration;
145✔
242
            event.properties.read_only_mode = this.userConfig.readOnly ? "true" : "false";
145!
243
            event.properties.disabled_tools = this.userConfig.disabledTools || [];
145!
244
            event.properties.confirmation_required_tools = this.userConfig.confirmationRequiredTools || [];
145!
245
            event.properties.previewFeatures = this.userConfig.previewFeatures;
145✔
246
            event.properties.embeddingProviderConfigured = !!this.userConfig.voyageApiKey;
145✔
247
        }
145✔
248
        if (command === "stop") {
305✔
249
            event.properties.runtime_duration_ms = Date.now() - this.startTime;
160✔
250
            if (error) {
160!
UNCOV
251
                event.properties.result = "failure";
×
UNCOV
252
                event.properties.reason = error.message;
×
UNCOV
253
            }
×
254
        }
160✔
255

256
        this.telemetry.emitEvents([event]);
305✔
257
    }
305✔
258

259
    public registerTools(): void {
3✔
260
        for (const toolConstructor of this.toolConstructors) {
165✔
261
            const tool = new toolConstructor({
6,159✔
262
                category: toolConstructor.category,
6,159✔
263
                operationType: toolConstructor.operationType,
6,159✔
264
                session: this.session,
6,159✔
265
                config: this.userConfig,
6,159✔
266
                telemetry: this.telemetry,
6,159✔
267
                elicitation: this.elicitation,
6,159✔
268
                uiRegistry: this.uiRegistry,
6,159✔
269
            });
6,159✔
270
            if (tool.register(this)) {
6,159✔
271
                this.tools.push(tool);
4,378✔
272
            }
4,378✔
273
        }
6,159✔
274
    }
165✔
275

276
    public registerResources(): void {
3✔
277
        for (const resourceConstructor of Resources) {
165✔
278
            const resource = new resourceConstructor(this.session, this.userConfig, this.telemetry);
495✔
279
            resource.register(this);
495✔
280
        }
495✔
281
    }
165✔
282

283
    private async validateConfig(): Promise<void> {
3✔
284
        // Validate connection string
285
        if (this.userConfig.connectionString) {
165!
286
            try {
8✔
287
                validateConnectionString(this.userConfig.connectionString, false);
8✔
288
            } catch (error) {
8!
289
                // eslint-disable-next-line no-console
UNCOV
290
                console.error("Connection string validation failed with error: ", error);
×
UNCOV
291
                throw new Error(
×
UNCOV
292
                    "Connection string validation failed with error: " +
×
UNCOV
293
                        (error instanceof Error ? error.message : String(error))
×
UNCOV
294
                );
×
UNCOV
295
            }
×
296
        }
8✔
297

298
        // Validate API client credentials
299
        if (this.userConfig.apiClientId && this.userConfig.apiClientSecret) {
165!
300
            try {
13✔
301
                if (!this.session.apiClient) {
13!
UNCOV
302
                    throw new Error("API client is not available.");
×
UNCOV
303
                }
×
304
                if (!this.userConfig.apiBaseUrl.startsWith("https://")) {
13!
UNCOV
305
                    const message =
×
UNCOV
306
                        "Failed to validate MongoDB Atlas the credentials from config: apiBaseUrl must start with https://";
×
307
                    // eslint-disable-next-line no-console
UNCOV
308
                    console.error(message);
×
309
                    throw new Error(message);
×
310
                }
×
311

312
                await this.session.apiClient.validateAuthConfig();
13✔
313
            } catch (error) {
13!
314
                if (this.userConfig.connectionString === undefined) {
×
315
                    // eslint-disable-next-line no-console
UNCOV
316
                    console.error("Failed to validate MongoDB Atlas the credentials from the config: ", error);
×
317

UNCOV
318
                    throw new Error(
×
UNCOV
319
                        "Failed to connect to MongoDB Atlas instance using the credentials from the config"
×
UNCOV
320
                    );
×
321
                }
×
322
                // eslint-disable-next-line no-console
UNCOV
323
                console.error(
×
324
                    "Failed to validate MongoDB Atlas credentials from the config, but validated the connection string."
×
325
                );
×
UNCOV
326
            }
×
327
        }
13✔
328
    }
165✔
329

330
    private async connectToConfigConnectionString(): Promise<void> {
3✔
331
        if (this.userConfig.connectionString) {
145!
332
            try {
8✔
333
                this.session.logger.info({
8✔
334
                    id: LogId.mongodbConnectTry,
8✔
335
                    context: "server",
8✔
336
                    message: `Detected a MongoDB connection string in the configuration, trying to connect...`,
8✔
337
                });
8✔
338
                await this.session.connectToConfiguredConnection();
8✔
339
            } catch (error) {
8✔
340
                // We don't throw an error here because we want to allow the server to start even if the connection string is invalid.
341
                this.session.logger.error({
2✔
342
                    id: LogId.mongodbConnectFailure,
2✔
343
                    context: "server",
2✔
344
                    message: `Failed to connect to MongoDB instance using the connection string from the config: ${error instanceof Error ? error.message : String(error)}`,
2!
345
                });
2✔
346
            }
2✔
347
        }
8✔
348
    }
145✔
349
}
3✔
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