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

mongodb-js / mongodb-mcp-server / 14667012576

25 Apr 2025 02:31PM UTC coverage: 81.696%. Remained the same
14667012576

Pull #125

github

fmenezes
fix: duplicated v on version bump
Pull Request #125: fix: duplicated v on version bump

128 of 208 branches covered (61.54%)

Branch coverage included in aggregate %.

720 of 830 relevant lines covered (86.75%)

44.18 hits per line

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

77.78
/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";
31✔
7
import { Telemetry } from "../telemetry/telemetry.js";
8
import { type ToolEvent } from "../telemetry/types.js";
9
import { UserConfig } from "../config.js";
10

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

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

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

19
    protected abstract category: ToolCategory;
20

21
    protected abstract operationType: OperationType;
22

23
    protected abstract description: string;
24

25
    protected abstract argsShape: ZodRawShape;
26

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

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

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

58
    public register(server: McpServer): void {
59
        if (!this.verifyAllowed()) {
810✔
60
            return;
210✔
61
        }
62

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

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

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

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

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

104
            return false;
×
105
        }
106

107
        return true;
600✔
108
    }
109

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