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

mongodb-js / mongodb-mcp-server / 14705884283

28 Apr 2025 10:45AM UTC coverage: 82.547% (+0.9%) from 81.696%
14705884283

Pull #131

github

fmenezes
fix: make ip changes smooth
Pull Request #131: feat: add atlas-connect-cluster tool

147 of 229 branches covered (64.19%)

Branch coverage included in aggregate %.

26 of 31 new or added lines in 4 files covered. (83.87%)

60 existing lines in 8 files now uncovered.

780 of 894 relevant lines covered (87.25%)

49.5 hits per line

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

90.14
/src/tools/tool.ts
1
import { z, type ZodRawShape, type ZodNever } from "zod";
2
import type { McpServer, ToolCallback } from "@modelcontextprotocol/sdk/server/mcp.js";
3
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
4
import { Session } from "../session.js";
5
import logger from "../logger.js";
31✔
6
import { mongoLogId } from "mongodb-log-writer";
7
import { Telemetry } from "../telemetry/telemetry.js";
8
import { type ToolEvent } from "../telemetry/types.js";
9
import { UserConfig } from "../config.js";
31✔
10
import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver";
11

12
export type ToolArgs<Args extends ZodRawShape> = z.objectOutputType<Args, ZodNever>;
13

14
export type OperationType = "metadata" | "read" | "create" | "delete" | "update" | "cluster";
15
export type ToolCategory = "mongodb" | "atlas";
16

31✔
17
export abstract class ToolBase {
18
    protected abstract name: string;
19

20
    protected abstract category: ToolCategory;
21

22
    protected abstract operationType: OperationType;
23

24
    protected abstract description: string;
25

26
    protected abstract argsShape: ZodRawShape;
27

28
    protected abstract execute(...args: Parameters<ToolCallback<typeof this.argsShape>>): Promise<CallToolResult>;
29

30
    constructor(
899✔
31
        protected readonly session: Session,
899✔
32
        protected readonly config: UserConfig,
899✔
33
        protected readonly telemetry: Telemetry
34
    ) {}
35

36
    /**
37
     * Creates and emits a tool telemetry event
38
     * @param startTime - Start time in milliseconds
39
     * @param result - Whether the command succeeded or failed
40
     * @param error - Optional error if the command failed
41
     */
42
    private async emitToolEvent(startTime: number, result: CallToolResult): Promise<void> {
227✔
43
        const duration = Date.now() - startTime;
227✔
44
        const event: ToolEvent = {
45
            timestamp: new Date().toISOString(),
46
            source: "mdbmcp",
47
            properties: {
48
                ...this.telemetry.getCommonProperties(),
49
                command: this.name,
50
                category: this.category,
51
                component: "tool",
52
                duration_ms: duration,
227✔
53
                result: result.isError ? "failure" : "success",
54
            },
55
        };
227✔
56
        await this.telemetry.emitEvents([event]);
57
    }
58

59
    public register(server: McpServer): void {
899✔
60
        if (!this.verifyAllowed()) {
254✔
61
            return;
62
        }
63

645✔
64
        const callback: ToolCallback<typeof this.argsShape> = async (...args) => {
227✔
65
            const startTime = Date.now();
227✔
66
            try {
227✔
67
                logger.debug(
68
                    mongoLogId(1_000_006),
227✔
69
                    "tool",
197✔
70
                    `Executing ${this.name} with args: ${JSON.stringify(args)}`
197✔
71
                );
72

30✔
73
                const result = await this.execute(...args);
30✔
74
                await this.emitToolEvent(startTime, result);
30✔
75
                return result;
30✔
76
            } catch (error: unknown) {
77
                logger.error(mongoLogId(1_000_000), "tool", `Error executing ${this.name}: ${error as string}`);
78
                const toolResult = await this.handleError(error, args[0] as ToolArgs<typeof this.argsShape>);
79
                await this.emitToolEvent(startTime, toolResult).catch(() => {});
645✔
80
                return toolResult;
81
            }
82
        };
83

84
        server.tool(this.name, this.description, this.argsShape, callback);
85
    }
645✔
86

225✔
87
    // Checks if a tool is allowed to run based on the config
225✔
88
    protected verifyAllowed(): boolean {
89
        let errorClarification: string | undefined;
225✔
90
        if (this.config.disabledTools.includes(this.category)) {
190✔
91
            errorClarification = `its category, \`${this.category}\`,`;
190✔
92
        } else if (this.config.disabledTools.includes(this.operationType)) {
190✔
93
            errorClarification = `its operation type, \`${this.operationType}\`,`;
94
        } else if (this.config.disabledTools.includes(this.name)) {
95
            errorClarification = `it`;
225✔
96
        }
225✔
97

225✔
98
        if (errorClarification) {
99
            logger.debug(
100
                mongoLogId(1_000_010),
225✔
101
                "tool",
225✔
102
                `Prevented registration of ${this.name} because ${errorClarification} is disabled in the config`
103
            );
104

225✔
105
            return false;
106
        }
107

108
        return true;
109
    }
110

111
    // This method is intended to be overridden by subclasses to handle errors
112
    protected handleError(
113
        error: unknown,
114
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
115
        args: ToolArgs<typeof this.argsShape>
657✔
116
    ): Promise<CallToolResult> | CallToolResult {
12✔
117
        return {
645!
UNCOV
118
            content: [
×
119
                {
645!
UNCOV
120
                    type: "text",
×
121
                    text: `Error running ${this.name}: ${error instanceof Error ? error.message : String(error)}`,
645!
UNCOV
122
                    isError: true,
×
123
                },
124
            ],
125
        };
657✔
126
    }
12✔
127

128
    protected async connectToMongoDB(connectionString: string): Promise<void> {
129
        const provider = await NodeDriverServiceProvider.connect(connectionString, {
130
            productDocsLink: "https://docs.mongodb.com/todo-mcp",
131
            productName: "MongoDB MCP",
132
            readConcern: {
12✔
133
                level: this.config.connectOptions.readConcern,
134
            },
135
            readPreference: this.config.connectOptions.readPreference,
645✔
136
            writeConcern: {
137
                w: this.config.connectOptions.writeConcern,
138
            },
139
            timeoutMS: this.config.connectOptions.timeoutMS,
140
        });
141

142
        this.session.serviceProvider = provider;
143
    }
144
}
4✔
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