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

mongodb-js / mongodb-mcp-server / 17615294858

10 Sep 2025 01:26PM UTC coverage: 81.412% (+0.1%) from 81.285%
17615294858

Pull #541

github

web-flow
Merge c934d8204 into 008ad6ba4
Pull Request #541: chore: warn about insecure httpHost usage - MCP-184

959 of 1273 branches covered (75.33%)

Branch coverage included in aggregate %.

2 of 6 new or added lines in 2 files covered. (33.33%)

6 existing lines in 1 file now uncovered.

4783 of 5780 relevant lines covered (82.75%)

46.43 hits per line

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

54.9
/src/transports/streamableHttp.ts
1
import express from "express";
1!
2
import type http from "http";
3
import { randomUUID } from "crypto";
1✔
4
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
1✔
5
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
1✔
6
import { LogId } from "../common/logger.js";
1✔
7
import { SessionStore } from "../common/sessionStore.js";
1✔
8
import { TransportRunnerBase, type TransportRunnerConfig } from "./base.js";
1✔
9

10
const JSON_RPC_ERROR_CODE_PROCESSING_REQUEST_FAILED = -32000;
1✔
11
const JSON_RPC_ERROR_CODE_SESSION_ID_REQUIRED = -32001;
1✔
12
const JSON_RPC_ERROR_CODE_SESSION_ID_INVALID = -32002;
1✔
13
const JSON_RPC_ERROR_CODE_SESSION_NOT_FOUND = -32003;
1✔
14
const JSON_RPC_ERROR_CODE_INVALID_REQUEST = -32004;
1✔
15

16
export class StreamableHttpRunner extends TransportRunnerBase {
1✔
17
    private httpServer: http.Server | undefined;
18
    private sessionStore!: SessionStore;
19

20
    constructor(config: TransportRunnerConfig) {
1✔
21
        super(config);
7✔
22
    }
7✔
23

24
    public get serverAddress(): string {
1✔
25
        const result = this.httpServer?.address();
18✔
26
        if (typeof result === "string") {
18!
27
            return result;
×
28
        }
×
29
        if (typeof result === "object" && result) {
18✔
30
            return `http://${result.address}:${result.port}`;
18✔
31
        }
18!
32

33
        throw new Error("Server is not started yet");
×
34
    }
18✔
35

36
    async start(): Promise<void> {
1✔
37
        const app = express();
7✔
38
        this.sessionStore = new SessionStore(
7✔
39
            this.userConfig.idleTimeoutMs,
7✔
40
            this.userConfig.notificationTimeoutMs,
7✔
41
            this.logger
7✔
42
        );
7✔
43

44
        app.enable("trust proxy"); // needed for reverse proxy support
7✔
45
        app.use(express.json());
7✔
46
        app.use((req, res, next) => {
7✔
47
            for (const [key, value] of Object.entries(this.userConfig.httpHeaders)) {
26✔
48
                const header = req.headers[key.toLowerCase()];
10✔
49
                if (!header || header !== value) {
10✔
50
                    res.status(403).send({ error: `Invalid value for header "${key}"` });
2✔
51
                    return;
2✔
52
                }
2✔
53
            }
10✔
54

55
            next();
24✔
56
        });
7✔
57

58
        const handleSessionRequest = async (req: express.Request, res: express.Response): Promise<void> => {
7✔
59
            const sessionId = req.headers["mcp-session-id"];
18✔
60
            if (!sessionId) {
18!
61
                res.status(400).json({
×
62
                    jsonrpc: "2.0",
×
63
                    error: {
×
64
                        code: JSON_RPC_ERROR_CODE_SESSION_ID_REQUIRED,
×
65
                        message: `session id is required`,
×
66
                    },
×
67
                });
×
68
                return;
×
69
            }
×
70
            if (typeof sessionId !== "string") {
18!
71
                res.status(400).json({
×
72
                    jsonrpc: "2.0",
×
73
                    error: {
×
74
                        code: JSON_RPC_ERROR_CODE_SESSION_ID_INVALID,
×
75
                        message: "session id is invalid",
×
76
                    },
×
77
                });
×
78
                return;
×
79
            }
×
80
            const transport = this.sessionStore.getSession(sessionId);
18✔
81
            if (!transport) {
18!
82
                res.status(404).json({
×
83
                    jsonrpc: "2.0",
×
84
                    error: {
×
85
                        code: JSON_RPC_ERROR_CODE_SESSION_NOT_FOUND,
×
86
                        message: "session not found",
×
87
                    },
×
88
                });
×
89
                return;
×
90
            }
×
91
            await transport.handleRequest(req, res, req.body);
18✔
92
        };
18✔
93

94
        app.post(
7✔
95
            "/mcp",
7✔
96
            this.withErrorHandling(async (req: express.Request, res: express.Response) => {
7✔
97
                const sessionId = req.headers["mcp-session-id"];
18✔
98
                if (sessionId) {
18✔
99
                    await handleSessionRequest(req, res);
12✔
100
                    return;
12✔
101
                }
12✔
102

103
                if (!isInitializeRequest(req.body)) {
18!
104
                    res.status(400).json({
×
105
                        jsonrpc: "2.0",
×
106
                        error: {
×
107
                            code: JSON_RPC_ERROR_CODE_INVALID_REQUEST,
×
108
                            message: `invalid request`,
×
109
                        },
×
110
                    });
×
111
                    return;
×
112
                }
✔
113

114
                const server = await this.setupServer();
6✔
115
                let keepAliveLoop: NodeJS.Timeout;
6✔
116
                const transport = new StreamableHTTPServerTransport({
6✔
117
                    sessionIdGenerator: (): string => randomUUID().toString(),
6✔
118
                    onsessioninitialized: (sessionId): void => {
6✔
119
                        server.session.logger.setAttribute("sessionId", sessionId);
6✔
120

121
                        this.sessionStore.setSession(sessionId, transport, server.session.logger);
6✔
122

123
                        let failedPings = 0;
6✔
124
                        // eslint-disable-next-line @typescript-eslint/no-misused-promises
125
                        keepAliveLoop = setInterval(async () => {
6✔
126
                            try {
×
127
                                server.session.logger.debug({
×
128
                                    id: LogId.streamableHttpTransportKeepAlive,
×
129
                                    context: "streamableHttpTransport",
×
130
                                    message: "Sending ping",
×
131
                                });
×
132

133
                                await transport.send({
×
134
                                    jsonrpc: "2.0",
×
135
                                    method: "ping",
×
136
                                });
×
137
                                failedPings = 0;
×
138
                            } catch (err) {
×
139
                                try {
×
140
                                    failedPings++;
×
141
                                    server.session.logger.warning({
×
142
                                        id: LogId.streamableHttpTransportKeepAliveFailure,
×
143
                                        context: "streamableHttpTransport",
×
144
                                        message: `Error sending ping (attempt #${failedPings}): ${err instanceof Error ? err.message : String(err)}`,
×
145
                                    });
×
146

147
                                    if (failedPings > 3) {
×
148
                                        clearInterval(keepAliveLoop);
×
149
                                        await transport.close();
×
150
                                    }
×
151
                                } catch {
×
152
                                    // Ignore the error of the transport close as there's nothing else
153
                                    // we can do at this point.
154
                                }
×
155
                            }
×
156
                        }, 30_000);
6✔
157
                    },
6✔
158
                    onsessionclosed: async (sessionId): Promise<void> => {
6✔
159
                        try {
×
160
                            await this.sessionStore.closeSession(sessionId, false);
×
161
                        } catch (error) {
×
162
                            this.logger.error({
×
163
                                id: LogId.streamableHttpTransportSessionCloseFailure,
×
164
                                context: "streamableHttpTransport",
×
165
                                message: `Error closing session ${sessionId}: ${error instanceof Error ? error.message : String(error)}`,
×
166
                            });
×
167
                        }
×
168
                    },
×
169
                });
6✔
170

171
                transport.onclose = (): void => {
6✔
172
                    clearInterval(keepAliveLoop);
6✔
173

174
                    server.close().catch((error) => {
6✔
175
                        this.logger.error({
×
176
                            id: LogId.streamableHttpTransportCloseFailure,
×
177
                            context: "streamableHttpTransport",
×
178
                            message: `Error closing server: ${error instanceof Error ? error.message : String(error)}`,
×
179
                        });
×
180
                    });
6✔
181
                };
6✔
182

183
                await server.connect(transport);
6✔
184

185
                await transport.handleRequest(req, res, req.body);
6✔
186
            })
7✔
187
        );
7✔
188

189
        app.get("/mcp", this.withErrorHandling(handleSessionRequest));
7✔
190
        app.delete("/mcp", this.withErrorHandling(handleSessionRequest));
7✔
191

192
        this.httpServer = await new Promise<http.Server>((resolve, reject) => {
7✔
193
            const result = app.listen(this.userConfig.httpPort, this.userConfig.httpHost, (err?: Error) => {
7✔
194
                if (err) {
7!
195
                    reject(err);
×
196
                    return;
×
197
                }
×
198
                resolve(result);
7✔
199
            });
7✔
200
        });
7✔
201

202
        this.logger.info({
7✔
203
            id: LogId.streamableHttpTransportStarted,
7✔
204
            context: "streamableHttpTransport",
7✔
205
            message: `Server started on ${this.serverAddress}`,
7✔
206
            noRedaction: true,
7✔
207
        });
7✔
208

209
        if (this.userConfig.httpHost === "0.0.0.0") {
7!
NEW
210
            this.logger.warning({
×
NEW
211
                id: LogId.streamableHttpTransportHttpHostWarning,
×
NEW
212
                context: "streamableHttpTransport",
×
NEW
213
                message: `Binding to \`0.0.0.0\` exposes the MCP Server to the entire local
×
214
   network, which allows other devices on the same network to
215
   potentially access the MCP Server. This is a security risk and could
216
   allow unauthorized access to your database context. `,
UNCOV
217
            });
×
UNCOV
218
        }
×
219
    }
7✔
220

221
    async closeTransport(): Promise<void> {
1✔
222
        await Promise.all([
6✔
223
            this.sessionStore.closeAllSessions(),
6✔
224
            new Promise<void>((resolve, reject) => {
6✔
225
                this.httpServer?.close((err) => {
6✔
226
                    if (err) {
6!
227
                        reject(err);
×
UNCOV
228
                        return;
×
UNCOV
229
                    }
×
230
                    resolve();
6✔
231
                });
6✔
232
            }),
6✔
233
        ]);
6✔
234
    }
6✔
235

236
    private withErrorHandling(
1✔
237
        fn: (req: express.Request, res: express.Response, next: express.NextFunction) => Promise<void>
21✔
238
    ) {
21✔
239
        return (req: express.Request, res: express.Response, next: express.NextFunction): void => {
21✔
240
            fn(req, res, next).catch((error) => {
24✔
241
                this.logger.error({
×
242
                    id: LogId.streamableHttpTransportRequestFailure,
×
243
                    context: "streamableHttpTransport",
×
244
                    message: `Error handling request: ${error instanceof Error ? error.message : String(error)}`,
×
245
                });
×
246
                res.status(400).json({
×
247
                    jsonrpc: "2.0",
×
248
                    error: {
×
249
                        code: JSON_RPC_ERROR_CODE_PROCESSING_REQUEST_FAILED,
×
250
                        message: `failed to handle request`,
×
251
                        data: error instanceof Error ? error.message : String(error),
×
UNCOV
252
                    },
×
UNCOV
253
                });
×
254
            });
24✔
255
        };
24✔
256
    }
21✔
257
}
1✔
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