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

mongodb-js / mongodb-mcp-server / 14670404051

25 Apr 2025 05:45PM UTC coverage: 81.886% (+0.2%) from 81.696%
14670404051

Pull #118

github

nirinchev
Merge branch 'main' into ni/conditional-connect
Pull Request #118: feat: update the connect tool based on connectivity status

129 of 210 branches covered (61.43%)

Branch coverage included in aggregate %.

59 of 67 new or added lines in 9 files covered. (88.06%)

10 existing lines in 1 file now uncovered.

748 of 861 relevant lines covered (86.88%)

47.74 hits per line

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

83.61
/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";
31✔
5
import { MongoDbTools } from "./tools/mongodb/tools.js";
31✔
6
import logger, { initializeLogger, LogId } from "./logger.js";
31✔
7
import { ObjectId } from "mongodb";
31✔
8
import { Telemetry } from "./telemetry/telemetry.js";
31✔
9
import { UserConfig } from "./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";
31✔
13
import assert from "assert";
31✔
14

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

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

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

36
    async connect(transport: Transport) {
37
        this.mcpServer.server.registerCapabilities({ logging: {} });
28✔
38
        this.registerTools();
28✔
39
        this.registerResources();
28✔
40

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

54
        assert(existingHandler, "No existing handler found for CallToolRequestSchema");
28✔
55

56
        this.mcpServer.server.setRequestHandler(CallToolRequestSchema, (request, extra): Promise<CallToolResult> => {
28✔
57
            if (!request.params.arguments) {
330✔
58
                request.params.arguments = {};
1✔
59
            }
60

61
            return existingHandler(request, extra);
330✔
62
        });
63

64
        await initializeLogger(this.mcpServer, this.userConfig.logPath);
28✔
65

66
        await this.mcpServer.connect(transport);
28✔
67

68
        this.mcpServer.server.oninitialized = () => {
28✔
69
            this.session.setAgentRunner(this.mcpServer.server.getClientVersion());
28✔
70
            this.session.sessionId = new ObjectId().toString();
28✔
71

72
            logger.info(
28✔
73
                LogId.serverInitialized,
74
                "server",
75
                `Server started with transport ${transport.constructor.name} and agent runner ${this.session.agentRunner?.name}`
76
            );
77

78
            this.emitServerEvent("start", Date.now() - this.startTime);
28✔
79
        };
80

81
        this.mcpServer.server.onclose = () => {
28✔
82
            const closeTime = Date.now();
28✔
83
            this.emitServerEvent("stop", Date.now() - closeTime);
28✔
84
        };
85

86
        this.mcpServer.server.onerror = (error: Error) => {
28✔
87
            const closeTime = Date.now();
×
88
            this.emitServerEvent("stop", Date.now() - closeTime, error);
×
89
        };
90
    }
91

92
    async close(): Promise<void> {
93
        await this.session.close();
28✔
94
        await this.mcpServer.close();
28✔
95
    }
96

97
    /**
98
     * Emits a server event
99
     * @param command - The server command (e.g., "start", "stop", "register", "deregister")
100
     * @param additionalProperties - Additional properties specific to the event
101
     */
102
    emitServerEvent(command: ServerCommand, commandDuration: number, error?: Error) {
103
        const event: ServerEvent = {
56✔
104
            timestamp: new Date().toISOString(),
105
            source: "mdbmcp",
106
            properties: {
107
                ...this.telemetry.getCommonProperties(),
108
                result: "success",
109
                duration_ms: commandDuration,
110
                component: "server",
111
                category: "other",
112
                command: command,
113
            },
114
        };
115

116
        if (command === "start") {
56✔
117
            event.properties.startup_time_ms = commandDuration;
28✔
118
        }
119
        if (command === "stop") {
56✔
120
            event.properties.runtime_duration_ms = Date.now() - this.startTime;
28✔
121
            if (error) {
28!
122
                event.properties.result = "failure";
×
123
                event.properties.reason = error.message;
×
124
            }
125
        }
126

127
        this.telemetry.emitEvents([event]).catch(() => {});
56✔
128
    }
129

130
    private registerTools() {
131
        for (const tool of [...AtlasTools, ...MongoDbTools]) {
28✔
132
            new tool(this.session, this.userConfig, this.telemetry).register(this.mcpServer);
840✔
133
        }
134
    }
135

136
    private registerResources() {
137
        this.mcpServer.resource(
28✔
138
            "config",
139
            "config://config",
140
            {
141
                description:
142
                    "Server configuration, supplied by the user either as environment variables or as startup arguments",
143
            },
144
            (uri) => {
NEW
145
                const result = {
×
146
                    telemetry: this.userConfig.telemetry,
147
                    logPath: this.userConfig.logPath,
148
                    connectionString: this.userConfig.connectionString
×
149
                        ? "set; no explicit connect needed, use switch-connection tool to connect to a different connection if necessary"
150
                        : "not set; before using any mongodb tool, you need to call the connect tool with a connection string",
151
                    connectOptions: this.userConfig.connectOptions,
152
                };
NEW
153
                return {
×
154
                    contents: [
155
                        {
156
                            text: JSON.stringify(result),
157
                            uri: uri.href,
158
                        },
159
                    ],
160
                };
161
            }
162
        );
163
        if (this.userConfig.connectionString) {
28✔
164
            this.mcpServer.resource(
1✔
165
                "connection-string",
166
                "config://connection-string",
167
                {
168
                    description: "Preconfigured connection string that will be used as a default in the `connect` tool",
169
                },
170
                (uri) => {
171
                    return {
×
172
                        contents: [
173
                            {
174
                                text: `Preconfigured connection string: ${this.userConfig.connectionString}`,
175
                                uri: uri.href,
176
                            },
177
                        ],
178
                    };
179
                }
180
            );
181
        }
182
    }
183
}
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