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

mongodb-js / mongodb-mcp-server / 16448811352

22 Jul 2025 03:31PM UTC coverage: 80.318% (-1.5%) from 81.82%
16448811352

Pull #387

github

web-flow
Merge 5a799519a into f8e500004
Pull Request #387: chore: update JIRA automation

538 of 706 branches covered (76.2%)

Branch coverage included in aggregate %.

3102 of 3826 relevant lines covered (81.08%)

47.32 hits per line

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

69.14
/src/server.ts
1
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
import { Session } from "./common/session.js";
3
import { 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 logger, { LogId, LoggerBase, McpLogger, DiskLogger, ConsoleLogger } from "./common/logger.js";
2✔
7
import { ObjectId } from "mongodb";
2✔
8
import { Telemetry } from "./telemetry/telemetry.js";
9
import { UserConfig } from "./common/config.js";
10
import { type ServerEvent } from "./telemetry/types.js";
11
import { type ServerCommand } from "./telemetry/types.js";
12
import { CallToolRequestSchema, CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2✔
13
import assert from "assert";
2✔
14
import { ToolBase } from "./tools/tool.js";
15

16
export interface ServerOptions {
17
    session: Session;
18
    userConfig: UserConfig;
19
    mcpServer: McpServer;
20
    telemetry: Telemetry;
21
}
22

23
export class Server {
2✔
24
    public readonly session: Session;
25
    public readonly mcpServer: McpServer;
26
    private readonly telemetry: Telemetry;
27
    public readonly userConfig: UserConfig;
28
    public readonly tools: ToolBase[] = [];
2✔
29
    private readonly startTime: number;
30

31
    constructor({ session, mcpServer, userConfig, telemetry }: ServerOptions) {
2✔
32
        this.startTime = Date.now();
64✔
33
        this.session = session;
64✔
34
        this.telemetry = telemetry;
64✔
35
        this.mcpServer = mcpServer;
64✔
36
        this.userConfig = userConfig;
64✔
37
    }
64✔
38

39
    async connect(transport: Transport): Promise<void> {
2✔
40
        await this.validateConfig();
64✔
41

42
        this.mcpServer.server.registerCapabilities({ logging: {} });
64✔
43

44
        this.registerTools();
64✔
45
        this.registerResources();
64✔
46

47
        // This is a workaround for an issue we've seen with some models, where they'll see that everything in the `arguments`
48
        // object is optional, and then not pass it at all. However, the MCP server expects the `arguments` object to be if
49
        // the tool accepts any arguments, even if they're all optional.
50
        //
51
        // see: https://github.com/modelcontextprotocol/typescript-sdk/blob/131776764536b5fdca642df51230a3746fb4ade0/src/server/mcp.ts#L705
52
        // Since paramsSchema here is not undefined, the server will create a non-optional z.object from it.
53
        const existingHandler = (
64✔
54
            this.mcpServer.server["_requestHandlers"] as Map<
64✔
55
                string,
56
                (request: unknown, extra: unknown) => Promise<CallToolResult>
57
            >
58
        ).get(CallToolRequestSchema.shape.method.value);
64✔
59

60
        assert(existingHandler, "No existing handler found for CallToolRequestSchema");
64✔
61

62
        this.mcpServer.server.setRequestHandler(CallToolRequestSchema, (request, extra): Promise<CallToolResult> => {
64✔
63
            if (!request.params.arguments) {
730✔
64
                request.params.arguments = {};
2✔
65
            }
2✔
66

67
            return existingHandler(request, extra);
730✔
68
        });
64✔
69

70
        const loggers: LoggerBase[] = [];
64✔
71
        if (this.userConfig.loggers.includes("mcp")) {
64✔
72
            loggers.push(new McpLogger(this.mcpServer));
4✔
73
        }
4✔
74
        if (this.userConfig.loggers.includes("disk")) {
64✔
75
            loggers.push(await DiskLogger.fromPath(this.userConfig.logPath));
4✔
76
        }
4✔
77
        if (this.userConfig.loggers.includes("stderr")) {
64✔
78
            loggers.push(new ConsoleLogger());
60✔
79
        }
60✔
80
        logger.setLoggers(...loggers);
64✔
81

82
        this.mcpServer.server.oninitialized = () => {
64✔
83
            this.session.setAgentRunner(this.mcpServer.server.getClientVersion());
64✔
84
            this.session.sessionId = new ObjectId().toString();
64✔
85

86
            logger.info(
64✔
87
                LogId.serverInitialized,
64✔
88
                "server",
64✔
89
                `Server started with transport ${transport.constructor.name} and agent runner ${this.session.agentRunner?.name}`
64✔
90
            );
64✔
91

92
            this.emitServerEvent("start", Date.now() - this.startTime);
64✔
93
        };
64✔
94

95
        this.mcpServer.server.onclose = () => {
64✔
96
            const closeTime = Date.now();
64✔
97
            this.emitServerEvent("stop", Date.now() - closeTime);
64✔
98
        };
64✔
99

100
        this.mcpServer.server.onerror = (error: Error) => {
64✔
101
            const closeTime = Date.now();
×
102
            this.emitServerEvent("stop", Date.now() - closeTime, error);
×
103
        };
×
104

105
        await this.mcpServer.connect(transport);
64✔
106
    }
64✔
107

108
    async close(): Promise<void> {
2✔
109
        await this.telemetry.close();
64✔
110
        await this.session.close();
64✔
111
        await this.mcpServer.close();
63✔
112
    }
64✔
113

114
    /**
115
     * Emits a server event
116
     * @param command - The server command (e.g., "start", "stop", "register", "deregister")
117
     * @param additionalProperties - Additional properties specific to the event
118
     */
119
    private emitServerEvent(command: ServerCommand, commandDuration: number, error?: Error) {
2✔
120
        const event: ServerEvent = {
128✔
121
            timestamp: new Date().toISOString(),
128✔
122
            source: "mdbmcp",
128✔
123
            properties: {
128✔
124
                result: "success",
128✔
125
                duration_ms: commandDuration,
128✔
126
                component: "server",
128✔
127
                category: "other",
128✔
128
                command: command,
128✔
129
            },
128✔
130
        };
128✔
131

132
        if (command === "start") {
128✔
133
            event.properties.startup_time_ms = commandDuration;
64✔
134
            event.properties.read_only_mode = this.userConfig.readOnly || false;
64✔
135
            event.properties.disabled_tools = this.userConfig.disabledTools || [];
64!
136
        }
64✔
137
        if (command === "stop") {
128✔
138
            event.properties.runtime_duration_ms = Date.now() - this.startTime;
64✔
139
            if (error) {
64!
140
                event.properties.result = "failure";
×
141
                event.properties.reason = error.message;
×
142
            }
×
143
        }
64✔
144

145
        this.telemetry.emitEvents([event]).catch(() => {});
128✔
146
    }
128✔
147

148
    private registerTools() {
2✔
149
        for (const toolConstructor of [...AtlasTools, ...MongoDbTools]) {
64✔
150
            const tool = new toolConstructor(this.session, this.userConfig, this.telemetry);
2,048✔
151
            if (tool.register(this)) {
2,048✔
152
                this.tools.push(tool);
1,681✔
153
            }
1,681✔
154
        }
2,048✔
155
    }
64✔
156

157
    private registerResources() {
2✔
158
        this.mcpServer.resource(
64✔
159
            "config",
64✔
160
            "config://config",
64✔
161
            {
64✔
162
                description:
64✔
163
                    "Server configuration, supplied by the user either as environment variables or as startup arguments",
64✔
164
            },
64✔
165
            (uri) => {
64✔
166
                const result = {
×
167
                    telemetry: this.userConfig.telemetry,
×
168
                    logPath: this.userConfig.logPath,
×
169
                    connectionString: this.userConfig.connectionString
×
170
                        ? "set; access to MongoDB tools are currently available to use"
×
171
                        : "not set; before using any MongoDB tool, you need to configure a connection string, alternatively you can setup MongoDB Atlas access, more info at 'https://github.com/mongodb-js/mongodb-mcp-server'.",
×
172
                    connectOptions: this.userConfig.connectOptions,
×
173
                    atlas:
×
174
                        this.userConfig.apiClientId && this.userConfig.apiClientSecret
×
175
                            ? "set; MongoDB Atlas tools are currently available to use"
×
176
                            : "not set; MongoDB Atlas tools are currently unavailable, to have access to MongoDB Atlas tools like creating clusters or connecting to clusters make sure to setup credentials, more info at 'https://github.com/mongodb-js/mongodb-mcp-server'.",
×
177
                };
×
178
                return {
×
179
                    contents: [
×
180
                        {
×
181
                            text: JSON.stringify(result),
×
182
                            mimeType: "application/json",
×
183
                            uri: uri.href,
×
184
                        },
×
185
                    ],
×
186
                };
×
187
            }
×
188
        );
64✔
189
    }
64✔
190

191
    private async validateConfig(): Promise<void> {
2✔
192
        const transport = this.userConfig.transport as string;
64✔
193
        if (transport !== "http" && transport !== "stdio") {
64!
194
            throw new Error(`Invalid transport: ${transport}`);
×
195
        }
×
196

197
        const telemetry = this.userConfig.telemetry as string;
64✔
198
        if (telemetry !== "enabled" && telemetry !== "disabled") {
64!
199
            throw new Error(`Invalid telemetry: ${telemetry}`);
×
200
        }
×
201

202
        if (this.userConfig.httpPort < 1 || this.userConfig.httpPort > 65535) {
64!
203
            throw new Error(`Invalid httpPort: ${this.userConfig.httpPort}`);
×
204
        }
×
205

206
        if (this.userConfig.loggers.length === 0) {
64!
207
            throw new Error("No loggers found in config");
×
208
        }
×
209

210
        const loggerTypes = new Set(this.userConfig.loggers);
64✔
211
        if (loggerTypes.size !== this.userConfig.loggers.length) {
64!
212
            throw new Error("Duplicate loggers found in config");
×
213
        }
×
214

215
        for (const loggerType of this.userConfig.loggers as string[]) {
64✔
216
            if (loggerType !== "mcp" && loggerType !== "disk" && loggerType !== "stderr") {
68!
217
                throw new Error(`Invalid logger: ${loggerType}`);
×
218
            }
×
219
        }
68✔
220

221
        if (this.userConfig.connectionString) {
64✔
222
            try {
2✔
223
                await this.session.connectToMongoDB(this.userConfig.connectionString, this.userConfig.connectOptions);
2✔
224
            } catch (error) {
2!
225
                console.error(
×
226
                    "Failed to connect to MongoDB instance using the connection string from the config: ",
×
227
                    error
×
228
                );
×
229
                throw new Error("Failed to connect to MongoDB instance using the connection string from the config");
×
230
            }
×
231
        }
2✔
232

233
        if (this.userConfig.apiClientId && this.userConfig.apiClientSecret) {
64✔
234
            try {
36✔
235
                await this.session.apiClient.validateAccessToken();
36✔
236
            } catch (error) {
36!
237
                if (this.userConfig.connectionString === undefined) {
×
238
                    console.error("Failed to validate MongoDB Atlas the credentials from the config: ", error);
×
239

240
                    throw new Error(
×
241
                        "Failed to connect to MongoDB Atlas instance using the credentials from the config"
×
242
                    );
×
243
                }
×
244
                console.error(
×
245
                    "Failed to validate MongoDB Atlas the credentials from the config, but validated the connection string."
×
246
                );
×
247
            }
×
248
        }
36✔
249
    }
64✔
250
}
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