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

mongodb-js / mongodb-mcp-server / 17543471199

08 Sep 2025 07:40AM UTC coverage: 80.922%. First build
17543471199

Pull #520

github

web-flow
Merge 558411c58 into a27f2d425
Pull Request #520: feat(atlas-local): Added Atlas Local List Deployments tool

927 of 1233 branches covered (75.18%)

Branch coverage included in aggregate %.

48 of 76 new or added lines in 5 files covered. (63.16%)

4723 of 5749 relevant lines covered (82.15%)

44.41 hits per line

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

78.73
/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 { AtlasLocalTools } from "./tools/atlasLocal/tools.js";
2✔
6
import { MongoDbTools } from "./tools/mongodb/tools.js";
2✔
7
import { Resources } from "./resources/resources.js";
2✔
8
import type { LogLevel } from "./common/logger.js";
9
import { LogId, McpLogger } from "./common/logger.js";
2✔
10
import type { Telemetry } from "./telemetry/telemetry.js";
11
import type { UserConfig } from "./common/config.js";
12
import { type ServerEvent } from "./telemetry/types.js";
13
import { type ServerCommand } from "./telemetry/types.js";
14
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
15
import {
2✔
16
    CallToolRequestSchema,
17
    SetLevelRequestSchema,
18
    SubscribeRequestSchema,
19
    UnsubscribeRequestSchema,
20
} from "@modelcontextprotocol/sdk/types.js";
21
import assert from "assert";
2✔
22
import type { ToolBase, ToolConstructor } from "./tools/tool.js";
23
import { validateConnectionString } from "./helpers/connectionOptions.js";
2✔
24
import { packageInfo } from "./common/packageInfo.js";
2✔
25
import { type ConnectionErrorHandler } from "./common/connectionErrorHandler.js";
26

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

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

43
    private _mcpLogLevel: LogLevel = "debug";
2✔
44

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

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

52
    constructor({ session, mcpServer, userConfig, telemetry, connectionErrorHandler }: ServerOptions) {
2✔
53
        this.startTime = Date.now();
64✔
54
        this.session = session;
64✔
55
        this.telemetry = telemetry;
64✔
56
        this.mcpServer = mcpServer;
64✔
57
        this.userConfig = userConfig;
64✔
58
        this.connectionErrorHandler = connectionErrorHandler;
64✔
59
    }
64✔
60

61
    async connect(transport: Transport): Promise<void> {
2✔
62
        // Resources are now reactive, so we register them ASAP so they can listen to events like
63
        // connection events.
64
        this.registerResources();
63✔
65
        await this.validateConfig();
63✔
66

67
        this.mcpServer.server.registerCapabilities({ logging: {}, resources: { listChanged: true, subscribe: true } });
63✔
68

69
        // TODO: Eventually we might want to make tools reactive too instead of relying on custom logic.
70
        this.registerTools();
63✔
71

72
        // Atlas Local tools are optional and require async initialization
73
        void this.registerAtlasLocalTools();
63✔
74

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

88
        assert(existingHandler, "No existing handler found for CallToolRequestSchema");
63✔
89

90
        this.mcpServer.server.setRequestHandler(CallToolRequestSchema, (request, extra): Promise<CallToolResult> => {
63✔
91
            if (!request.params.arguments) {
464✔
92
                request.params.arguments = {};
1✔
93
            }
1✔
94

95
            return existingHandler(request, extra);
464✔
96
        });
63✔
97

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

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

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

123
            this._mcpLogLevel = params.level;
×
124
            return {};
×
125
        });
63✔
126

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

137
            this.emitServerEvent("start", Date.now() - this.startTime);
63✔
138
        };
63✔
139

140
        this.mcpServer.server.onclose = (): void => {
63✔
141
            const closeTime = Date.now();
63✔
142
            this.emitServerEvent("stop", Date.now() - closeTime);
63✔
143
        };
63✔
144

145
        this.mcpServer.server.onerror = (error: Error): void => {
63✔
146
            const closeTime = Date.now();
×
147
            this.emitServerEvent("stop", Date.now() - closeTime, error);
×
148
        };
×
149

150
        await this.mcpServer.connect(transport);
63✔
151
    }
63✔
152

153
    async close(): Promise<void> {
2✔
154
        await this.telemetry.close();
63✔
155
        await this.session.close();
63✔
156
        await this.mcpServer.close();
63✔
157
    }
63✔
158

159
    public sendResourceListChanged(): void {
2✔
160
        this.mcpServer.sendResourceListChanged();
538✔
161
    }
538✔
162

163
    public sendResourceUpdated(uri: string): void {
2✔
164
        if (this.subscriptions.has(uri)) {
538!
165
            void this.mcpServer.server.sendResourceUpdated({ uri });
20✔
166
        }
20✔
167
    }
538✔
168

169
    /**
170
     * Emits a server event
171
     * @param command - The server command (e.g., "start", "stop", "register", "deregister")
172
     * @param additionalProperties - Additional properties specific to the event
173
     */
174
    private emitServerEvent(command: ServerCommand, commandDuration: number, error?: Error): void {
2✔
175
        const event: ServerEvent = {
126✔
176
            timestamp: new Date().toISOString(),
126✔
177
            source: "mdbmcp",
126✔
178
            properties: {
126✔
179
                result: "success",
126✔
180
                duration_ms: commandDuration,
126✔
181
                component: "server",
126✔
182
                category: "other",
126✔
183
                command: command,
126✔
184
            },
126✔
185
        };
126✔
186

187
        if (command === "start") {
126✔
188
            event.properties.startup_time_ms = commandDuration;
63✔
189
            event.properties.read_only_mode = this.userConfig.readOnly || false;
63✔
190
            event.properties.disabled_tools = this.userConfig.disabledTools || [];
63!
191
        }
63✔
192
        if (command === "stop") {
126✔
193
            event.properties.runtime_duration_ms = Date.now() - this.startTime;
63✔
194
            if (error) {
63!
195
                event.properties.result = "failure";
×
196
                event.properties.reason = error.message;
×
197
            }
×
198
        }
63✔
199

200
        this.telemetry.emitEvents([event]).catch(() => {});
126✔
201
    }
126✔
202

203
    private async registerAtlasLocalTools(): Promise<void> {
2✔
204
        // If Atlas Local tools are disabled, don't attempt to connect to the client
205
        if (this.userConfig.disabledTools.includes("atlas-local")) {
63!
NEW
206
            return;
×
NEW
207
        }
×
208

209
        try {
63✔
210
            // Import Atlas Local client asyncronously
211
            // This will fail on unsupported platforms
212
            const { Client: AtlasLocalClient } = await import("@mongodb-js-preview/atlas-local");
63✔
213

214
            // Connect to Atlas Local client
215
            // This will fail if docker is not running
216
            const client = AtlasLocalClient.connect();
63✔
217

218
            // Set Atlas Local client
219
            this.session.setAtlasLocalClient(client);
63✔
220

221
            // Register Atlas Local tools
222
            this.registerToolInstances(AtlasLocalTools);
63✔
223
        } catch (error) {
63!
NEW
224
            console.warn(
×
NEW
225
                "Failed to initialize Atlas Local client, atlas-local tools will be disabled (error: ",
×
NEW
226
                error,
×
NEW
227
                ")"
×
NEW
228
            );
×
NEW
229
        }
×
230
    }
63✔
231

232
    private registerTools(): void {
2✔
233
        this.registerToolInstances([...AtlasTools, ...MongoDbTools]);
63✔
234
    }
63✔
235

236
    private registerToolInstances(tools: Array<ToolConstructor>): void {
2✔
237
        for (const toolConstructor of tools) {
126✔
238
            const tool = new toolConstructor(this.session, this.userConfig, this.telemetry);
2,142✔
239
            if (tool.register(this)) {
2,142✔
240
                this.tools.push(tool);
1,481✔
241
            }
1,481✔
242
        }
2,142✔
243
    }
126✔
244

245
    private registerResources(): void {
2✔
246
        for (const resourceConstructor of Resources) {
63✔
247
            const resource = new resourceConstructor(this.session, this.userConfig, this.telemetry);
189✔
248
            resource.register(this);
189✔
249
        }
189✔
250
    }
63✔
251

252
    private async validateConfig(): Promise<void> {
2✔
253
        // Validate connection string
254
        if (this.userConfig.connectionString) {
63!
255
            try {
5✔
256
                validateConnectionString(this.userConfig.connectionString, false);
5✔
257
            } catch (error) {
5!
258
                console.error("Connection string validation failed with error: ", error);
×
259
                throw new Error(
×
260
                    "Connection string validation failed with error: " +
×
261
                        (error instanceof Error ? error.message : String(error))
×
262
                );
×
263
            }
×
264
        }
5✔
265

266
        // Validate API client credentials
267
        if (this.userConfig.apiClientId && this.userConfig.apiClientSecret) {
63✔
268
            try {
9✔
269
                await this.session.apiClient.validateAccessToken();
9✔
270
            } catch (error) {
9!
271
                if (this.userConfig.connectionString === undefined) {
×
272
                    console.error("Failed to validate MongoDB Atlas the credentials from the config: ", error);
×
273

274
                    throw new Error(
×
275
                        "Failed to connect to MongoDB Atlas instance using the credentials from the config"
×
276
                    );
×
277
                }
×
278
                console.error(
×
279
                    "Failed to validate MongoDB Atlas the credentials from the config, but validated the connection string."
×
280
                );
×
281
            }
×
282
        }
9✔
283
    }
63✔
284

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