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

mongodb-js / mongodb-mcp-server / 17863910702

19 Sep 2025 04:19PM UTC coverage: 82.391% (+0.5%) from 81.843%
17863910702

Pull #536

github

web-flow
Merge e1c95bda3 into c10955af6
Pull Request #536: fix: add guards against possible memory overflow in find and aggregate tools MCP-21

1083 of 1425 branches covered (76.0%)

Branch coverage included in aggregate %.

275 of 289 new or added lines in 7 files covered. (95.16%)

2 existing lines in 1 file now uncovered.

5238 of 6247 relevant lines covered (83.85%)

64.29 hits per line

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

96.67
/src/helpers/collectCursorUntilMaxBytes.ts
1
import { calculateObjectSize } from "bson";
2✔
2
import type { AggregationCursor, FindCursor } from "mongodb";
3

4
export function getResponseBytesLimit(
2✔
5
    toolResponseBytesLimit: number | undefined | null,
66✔
6
    configuredMaxBytesPerQuery: unknown
66✔
7
): {
8
    cappedBy: "config.maxBytesPerQuery" | "tool.responseBytesLimit" | undefined;
9
    limit: number;
10
} {
66✔
11
    const configuredLimit: number = parseInt(String(configuredMaxBytesPerQuery), 10);
66✔
12

13
    // Setting configured maxBytesPerQuery to negative, zero or nullish is
14
    // equivalent to disabling the max limit applied on documents
15
    const configuredLimitIsNotApplicable = Number.isNaN(configuredLimit) || configuredLimit <= 0;
66✔
16

17
    // It's possible to have tool parameter responseBytesLimit as null or
18
    // negative values in which case we consider that no limit is to be
19
    // applied from tool call perspective unless we have a maxBytesPerQuery
20
    // configured.
21
    const toolResponseLimitIsNotApplicable = typeof toolResponseBytesLimit !== "number" || toolResponseBytesLimit <= 0;
66✔
22

23
    if (configuredLimitIsNotApplicable) {
66✔
24
        return {
11✔
25
            cappedBy: toolResponseLimitIsNotApplicable ? undefined : "tool.responseBytesLimit",
11✔
26
            limit: toolResponseLimitIsNotApplicable ? 0 : toolResponseBytesLimit,
11✔
27
        };
11✔
28
    }
11✔
29

30
    if (toolResponseLimitIsNotApplicable) {
66!
NEW
31
        return { cappedBy: "config.maxBytesPerQuery", limit: configuredLimit };
×
NEW
32
    }
✔
33

34
    return {
55✔
35
        cappedBy: configuredLimit < toolResponseBytesLimit ? "config.maxBytesPerQuery" : "tool.responseBytesLimit",
66✔
36
        limit: Math.min(toolResponseBytesLimit, configuredLimit),
66✔
37
    };
66✔
38
}
66✔
39

40
/**
41
 * This function attempts to put a guard rail against accidental memory overflow
42
 * on the MCP server.
43
 *
44
 * The cursor is iterated until we can predict that fetching next doc won't
45
 * exceed the derived limit on number of bytes for the tool call. The derived
46
 * limit takes into account the limit provided from the Tool's interface and the
47
 * configured maxBytesPerQuery for the server.
48
 */
49
export async function collectCursorUntilMaxBytesLimit<T = unknown>({
66✔
50
    cursor,
66✔
51
    toolResponseBytesLimit,
66✔
52
    configuredMaxBytesPerQuery,
66✔
53
    abortSignal,
66✔
54
}: {
66✔
55
    cursor: FindCursor<T> | AggregationCursor<T>;
56
    toolResponseBytesLimit: number | undefined | null;
57
    configuredMaxBytesPerQuery: unknown;
58
    abortSignal?: AbortSignal;
59
}): Promise<{ cappedBy: "config.maxBytesPerQuery" | "tool.responseBytesLimit" | undefined; documents: T[] }> {
66✔
60
    const { limit: maxBytesPerQuery, cappedBy } = getResponseBytesLimit(
66✔
61
        toolResponseBytesLimit,
66✔
62
        configuredMaxBytesPerQuery
66✔
63
    );
66✔
64

65
    // It's possible to have no limit on the cursor response by setting both the
66
    // config.maxBytesPerQuery and tool.responseBytesLimit to nullish or
67
    // negative values.
68
    if (maxBytesPerQuery <= 0) {
66✔
69
        return {
3✔
70
            cappedBy,
3✔
71
            documents: await cursor.toArray(),
3✔
72
        };
3✔
73
    }
3✔
74

75
    let wasCapped: boolean = false;
63✔
76
    let totalBytes = 0;
63✔
77
    const bufferedDocuments: T[] = [];
63✔
78
    while (true) {
66✔
79
        if (abortSignal?.aborted) {
3,372✔
80
            break;
1✔
81
        }
1✔
82

83
        // If the cursor is empty then there is nothing for us to do anymore.
84
        const nextDocument = await cursor.tryNext();
3,371✔
85
        if (!nextDocument) {
3,372✔
86
            break;
47✔
87
        }
47✔
88

89
        const nextDocumentSize = calculateObjectSize(nextDocument);
3,324✔
90
        if (totalBytes + nextDocumentSize >= maxBytesPerQuery) {
3,372✔
91
            wasCapped = true;
15✔
92
            break;
15✔
93
        }
15✔
94

95
        totalBytes += nextDocumentSize;
3,309✔
96
        bufferedDocuments.push(nextDocument);
3,309✔
97
    }
3,309✔
98

99
    return {
63✔
100
        cappedBy: wasCapped ? cappedBy : undefined,
66✔
101
        documents: bufferedDocuments,
66✔
102
    };
66✔
103
}
66✔
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