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

mongodb-js / mongodb-mcp-server / 14670699273

25 Apr 2025 06:04PM UTC coverage: 82.51% (+0.3%) from 82.257%
14670699273

Pull #130

github

web-flow
Merge branch 'main' into read-only
Pull Request #130: feat: add readOnly flag

141 of 217 branches covered (64.98%)

Branch coverage included in aggregate %.

4 of 5 new or added lines in 3 files covered. (80.0%)

10 existing lines in 2 files now uncovered.

727 of 835 relevant lines covered (87.07%)

92.47 hits per line

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

87.3
/src/server.ts
1
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
import { Session } from "./session.js";
3
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
4
import { AtlasTools } from "./tools/atlas/tools.js";
62✔
5
import { MongoDbTools } from "./tools/mongodb/tools.js";
62✔
6
import logger, { initializeLogger } from "./logger.js";
62✔
7
import { mongoLogId } from "mongodb-log-writer";
62✔
8
import { ObjectId } from "mongodb";
62✔
9
import { Telemetry } from "./telemetry/telemetry.js";
62✔
10
import { UserConfig } from "./config.js";
11
import { type ServerEvent } from "./telemetry/types.js";
12
import { type ServerCommand } from "./telemetry/types.js";
13
import { CallToolRequestSchema, CallToolResult } from "@modelcontextprotocol/sdk/types.js";
62✔
14
import assert from "assert";
62✔
15

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

22
export class Server {
62✔
23
    public readonly session: Session;
24
    private readonly mcpServer: McpServer;
25
    private readonly telemetry: Telemetry;
26
    private readonly userConfig: UserConfig;
27
    private readonly startTime: number;
28

29
    constructor({ session, mcpServer, userConfig }: ServerOptions) {
30
        this.startTime = Date.now();
56✔
31
        this.session = session;
56✔
32
        this.telemetry = new Telemetry(session);
56✔
33
        this.mcpServer = mcpServer;
56✔
34
        this.userConfig = userConfig;
56✔
35
    }
36

37
    async connect(transport: Transport) {
38
        this.mcpServer.server.registerCapabilities({ logging: {} });
56✔
39

40
        // Log read-only mode status if enabled
41
        if (this.userConfig.readOnly) {
56✔
42
            logger.info(
2✔
43
                mongoLogId(1_000_005),
44
                "server",
45
                "Server starting in READ-ONLY mode. Only read and metadata operations will be available."
46
            );
47
        }
48

49
        this.registerTools();
56✔
50
        this.registerResources();
56✔
51

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

65
        assert(existingHandler, "No existing handler found for CallToolRequestSchema");
56✔
66

67
        this.mcpServer.server.setRequestHandler(CallToolRequestSchema, (request, extra): Promise<CallToolResult> => {
56✔
68
            if (!request.params.arguments) {
660✔
69
                request.params.arguments = {};
4✔
70
            }
71

72
            return existingHandler(request, extra);
660✔
73
        });
74

75
        await initializeLogger(this.mcpServer, this.userConfig.logPath);
56✔
76

77
        await this.mcpServer.connect(transport);
56✔
78

79
        this.mcpServer.server.oninitialized = () => {
56✔
80
            this.session.setAgentRunner(this.mcpServer.server.getClientVersion());
56✔
81
            this.session.sessionId = new ObjectId().toString();
56✔
82

83
            logger.info(
56✔
84
                mongoLogId(1_000_004),
85
                "server",
86
                `Server started with transport ${transport.constructor.name} and agent runner ${this.session.agentRunner?.name}`
87
            );
88

89
            this.emitServerEvent("start", Date.now() - this.startTime);
56✔
90
        };
91

92
        this.mcpServer.server.onclose = () => {
56✔
93
            const closeTime = Date.now();
56✔
94
            this.emitServerEvent("stop", Date.now() - closeTime);
56✔
95
        };
96

97
        this.mcpServer.server.onerror = (error: Error) => {
56✔
UNCOV
98
            const closeTime = Date.now();
×
UNCOV
99
            this.emitServerEvent("stop", Date.now() - closeTime, error);
×
100
        };
101
    }
102

103
    async close(): Promise<void> {
104
        await this.session.close();
56✔
105
        await this.mcpServer.close();
56✔
106
    }
107

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

127
        if (command === "start") {
112✔
128
            event.properties.startup_time_ms = commandDuration;
56✔
129
            event.properties.read_only_mode = this.userConfig.readOnly || false;
56✔
130
        }
131
        if (command === "stop") {
112✔
132
            event.properties.runtime_duration_ms = Date.now() - this.startTime;
56✔
133
            if (error) {
56!
UNCOV
134
                event.properties.result = "failure";
×
UNCOV
135
                event.properties.reason = error.message;
×
136
            }
137
        }
138

139
        this.telemetry.emitEvents([event]).catch(() => {});
112✔
140
    }
141

142
    private registerTools() {
143
        for (const tool of [...AtlasTools, ...MongoDbTools]) {
56✔
144
            new tool(this.session, this.userConfig, this.telemetry).register(this.mcpServer);
1,680✔
145
        }
146
    }
147

148
    private registerResources() {
149
        if (this.userConfig.connectionString) {
56!
UNCOV
150
            this.mcpServer.resource(
×
151
                "connection-string",
152
                "config://connection-string",
153
                {
154
                    description: "Preconfigured connection string that will be used as a default in the `connect` tool",
155
                },
156
                (uri) => {
UNCOV
157
                    return {
×
158
                        contents: [
159
                            {
160
                                text: `Preconfigured connection string: ${this.userConfig.connectionString}`,
161
                                uri: uri.href,
162
                            },
163
                        ],
164
                    };
165
                }
166
            );
167
        }
168
    }
169
}
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